systemvm-template: support on-demand download during setup and registration (#11656)

Bundling all hypervisor SystemVM templates in release packages simplifies installs but inflates build time and artifact size. This change enables downloading templates on demand when they’re not found after package installation. The download path is wired into both cloud-setup-management and the existing SystemVM template registration flow.

For connected or mirrored environments, a repository URL prefix can be provided to support air-gapped setups: pass --systemvm-templates-repository <URL-prefix> to cloud-setup-management, or set system.vm.templates.download.repository=<URL-prefix> in server.properties for post-setup registration.

If templates are already present (bundled or preseeded), behavior is unchanged and no download is attempted.

---------

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2025-12-26 11:36:32 +05:30 committed by GitHub
parent 5bf869c803
commit 34b8870f59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 2397 additions and 546 deletions

View File

@ -36,6 +36,106 @@ from cloudutils.cloudException import CloudRuntimeException, CloudInternalExcept
from cloudutils.globalEnv import globalEnv
from cloudutils.serviceConfigServer import cloudManagementConfig
from optparse import OptionParser
import urllib.request
import configparser
import hashlib
SYSTEMVM_TEMPLATES_PATH = "/usr/share/cloudstack-management/templates/systemvm"
SYSTEMVM_TEMPLATES_METADATA_FILE = SYSTEMVM_TEMPLATES_PATH + "/metadata.ini"
def verify_sha512_checksum(file_path, expected_checksum):
sha512 = hashlib.sha512()
try:
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha512.update(chunk)
return sha512.hexdigest().lower() == expected_checksum.lower()
except Exception as e:
print(f"Failed to verify checksum for {file_path}: {e}")
return False
def download_file(url, dest_path, chunk_size=8 * 1024 * 1024):
"""
Downloads a file from the given URL to the specified destination path in chunks.
"""
try:
with urllib.request.urlopen(url) as response:
total_size = response.length if response.length else None
downloaded = 0
try:
with open(dest_path, 'wb') as out_file:
while True:
chunk = response.read(chunk_size)
if not chunk:
break
out_file.write(chunk)
downloaded += len(chunk)
if total_size:
print(f"Downloaded {downloaded / (1024 * 1024):.2f}MB of {total_size / (1024 * 1024):.2f}MB", end='\r')
except PermissionError as pe:
print(f"Permission denied: {dest_path}")
raise
print(f"\nDownloaded file from {url} to {dest_path}")
except Exception as e:
print(f"Failed to download file: {e}")
raise
def download_template_if_needed(template, url, filename, checksum):
dest_path = os.path.join(SYSTEMVM_TEMPLATES_PATH, filename)
if os.path.exists(dest_path):
if checksum and verify_sha512_checksum(dest_path, checksum):
print(f"{template} System VM template already exists at {dest_path} with valid checksum, skipping download.")
return
else:
print(f"{template} System VM template at {dest_path} has invalid or missing checksum, re-downloading...")
else:
print(f"Downloading {template} System VM template from {url} to {dest_path}...")
try:
download_file(url, dest_path)
#After download, verify checksum if provided
if checksum:
if verify_sha512_checksum(dest_path, checksum):
print(f"{template} System VM template downloaded and verified successfully.")
else:
print(f"ERROR: Checksum verification failed for {template} System VM template after download.")
except Exception as e:
print(f"ERROR: Failed to download {template} System VM template: {e}")
def collect_template_metadata(selected_templates, options):
template_metadata_list = []
if not os.path.exists(SYSTEMVM_TEMPLATES_METADATA_FILE):
print(f"ERROR: System VM templates metadata file not found at {SYSTEMVM_TEMPLATES_METADATA_FILE}, cannot download templates.")
sys.exit(1)
config = configparser.ConfigParser()
config.read(SYSTEMVM_TEMPLATES_METADATA_FILE)
template_repo_url = None
if options.systemvm_templates_repository:
if "default" in config and "downloadrepository" in config["default"]:
template_repo_url = config["default"]["downloadrepository"].strip()
if not template_repo_url:
print("ERROR: downloadrepository value is empty in metadata file, cannot use --systemvm-template-repository option.")
sys.exit(1)
for template in selected_templates:
if template in config:
url = config[template].get("downloadurl")
filename = config[template].get("filename")
checksum = config[template].get("checksum")
if url and filename:
if template_repo_url:
url = url.replace(template_repo_url, options.systemvm_templates_repository)
template_metadata_list.append({
"template": template,
"url": url,
"filename": filename,
"checksum": checksum
})
else:
print(f"ERROR: URL or filename not found for {template} System VM template in metadata.")
sys.exit(1)
else:
print(f"ERROR: No metadata found for {template} System VM template.")
sys.exit(1)
return template_metadata_list
if __name__ == '__main__':
initLoging("@MSLOGDIR@/setupManagement.log")
@ -45,6 +145,16 @@ if __name__ == '__main__':
parser.add_option("--https", action="store_true", dest="https", help="Enable HTTPs connection of management server")
parser.add_option("--tomcat7", action="store_true", dest="tomcat7", help="Depreciated option, don't use it")
parser.add_option("--no-start", action="store_true", dest="nostart", help="Do not start management server after successful configuration")
parser.add_option(
"--systemvm-templates",
dest="systemvm_templates",
help="Specify System VM templates to download: all, kvm-aarch64, kvm-x86_64, xenserver, vmware or comma-separated list of hypervisor combinations (e.g., kvm-x86_64,xenserver). Default is kvm-x86_64.",
)
parser.add_option(
"--systemvm-templates-repository",
dest="systemvm_templates_repository",
help="Specify the URL to download System VM templates from."
)
(options, args) = parser.parse_args()
if options.https:
glbEnv.svrMode = "HttpsServer"
@ -53,6 +163,34 @@ if __name__ == '__main__':
if options.nostart:
glbEnv.noStart = True
available_templates = ["kvm-aarch64", "kvm-x86_64", "xenserver", "vmware"]
templates_arg = options.systemvm_templates
selected_templates = ["kvm-x86_64"]
if templates_arg:
templates_list = [t.strip().lower() for t in templates_arg.split(",")]
if "all" in templates_list:
if len(templates_list) > 1:
print("WARNING: 'all' specified for System VM templates, ignoring other specified templates.")
selected_templates = available_templates
else:
invalid_templates = []
for t in templates_list:
if t in available_templates:
if t not in selected_templates:
selected_templates.append(t)
else:
if t not in invalid_templates:
invalid_templates.append(t)
if invalid_templates:
print(f"ERROR: Invalid System VM template names provided: {', '.join(invalid_templates)}")
sys.exit(1)
print(f"Selected systemvm templates to download: {', '.join(selected_templates) if selected_templates else 'None'}")
template_metadata_list = []
if selected_templates:
template_metadata_list = collect_template_metadata(selected_templates, options)
glbEnv.mode = "Server"
print("Starting to configure CloudStack Management Server:")
@ -74,3 +212,6 @@ if __name__ == '__main__':
syscfg.restore()
except:
pass
for meta in template_metadata_list:
download_template_if_needed(meta["template"], meta["url"], meta["filename"], meta["checksum"])

View File

@ -62,3 +62,8 @@ extensions.deployment.mode=@EXTENSIONSDEPLOYMENTMODE@
# Thread pool configuration
#threads.min=10
#threads.max=500
# The URL prefix for the system VM templates repository. When downloading system VM templates, the server replaces the
# `downloadrepository` key value from the metadata file in template URLs. If not specified, the original template URL
# will be used for download.
# system.vm.templates.download.repository=http://download.cloudstack.org/systemvm/

View File

@ -31,7 +31,8 @@ public class DataCenterDetailsDaoImpl extends ResourceDetailsDaoBase<DataCenterD
private final SearchBuilder<DataCenterDetailVO> DetailSearch;
DataCenterDetailsDaoImpl() {
public DataCenterDetailsDaoImpl() {
super();
DetailSearch = createSearchBuilder();
DetailSearch.and("zoneId", DetailSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ);

View File

@ -94,7 +94,7 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
List<VMTemplateVO> listByParentTemplatetId(long parentTemplatetId);
VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch);
VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch);
List<VMTemplateVO> findTemplatesLinkedToUserdata(long userdataId);
@ -103,4 +103,7 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
List<Long> listIdsByTemplateTag(String tag);
List<Long> listIdsByExtensionId(long extensionId);
VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
CPU.CPUArch arch, String urlPathSuffix);
}

View File

@ -245,13 +245,17 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
@Override
public VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch) {
public VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch) {
SearchBuilder<VMTemplateVO> sb = createSearchBuilder();
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<VMTemplateVO> sc = sb.create();
sc.setParameters("name", name);
if (hypervisorType != null) {
sc.setParameters("hypervisorType", hypervisorType);
}
if (arch != null) {
sc.setParameters("arch", arch);
}
@ -850,6 +854,37 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
return customSearch(sc, null);
}
@Override
public VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
CPU.CPUArch arch, String urlPathSuffix) {
if (StringUtils.isBlank(urlPathSuffix)) {
return null;
}
SearchBuilder<VMTemplateVO> sb = createSearchBuilder();
sb.and("templateType", sb.entity().getTemplateType(), SearchCriteria.Op.EQ);
sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
sb.and("urlPathSuffix", sb.entity().getUrl(), SearchCriteria.Op.LIKE);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<VMTemplateVO> sc = sb.create();
sc.setParameters("templateType", TemplateType.SYSTEM);
if (hypervisorType != null) {
sc.setParameters("hypervisorType", hypervisorType);
}
if (arch != null) {
sc.setParameters("arch", arch);
}
sc.setParameters("urlPathSuffix", "%" + urlPathSuffix);
sc.setParameters("state", VirtualMachineTemplate.State.Active);
Filter filter = new Filter(VMTemplateVO.class, "id", false, null, 1L);
List<VMTemplateVO> templates = listBy(sc, filter);
if (CollectionUtils.isNotEmpty(templates)) {
return templates.get(0);
}
return null;
}
@Override
public boolean updateState(
com.cloud.template.VirtualMachineTemplate.State currentState,

View File

@ -76,7 +76,8 @@ public class VMTemplateDaoImplTest {
VMTemplateVO expectedTemplate = new VMTemplateVO();
List<VMTemplateVO> returnedList = Collections.singletonList(expectedTemplate);
doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault());
VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.KVM,
CPU.CPUArch.getDefault());
assertNotNull("Expected a non-null template", result);
assertEquals("Expected the returned template to be the first element", expectedTemplate, result);
}
@ -85,7 +86,8 @@ public class VMTemplateDaoImplTest {
public void testFindLatestTemplateByName_ReturnsNullWhenNoTemplateFound() {
List<VMTemplateVO> emptyList = Collections.emptyList();
doReturn(emptyList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault());
VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.VMware,
CPU.CPUArch.getDefault());
assertNull("Expected null when no templates are found", result);
}
@ -94,7 +96,8 @@ public class VMTemplateDaoImplTest {
VMTemplateVO expectedTemplate = new VMTemplateVO();
List<VMTemplateVO> returnedList = Collections.singletonList(expectedTemplate);
doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findLatestTemplateByName("test", null);
VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.XenServer,
null);
assertNotNull("Expected a non-null template even if arch is null", result);
assertEquals("Expected the returned template to be the first element", expectedTemplate, result);
}
@ -337,4 +340,82 @@ public class VMTemplateDaoImplTest {
VMTemplateVO readyTemplate = templateDao.findSystemVMReadyTemplate(zoneId, Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64.getType());
Assert.assertEquals(CPU.CPUArch.arm64, readyTemplate.getArch());
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_ReturnsTemplate() {
VMTemplateVO expectedTemplate = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(expectedTemplate);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
List<VMTemplateVO> templates = Collections.singletonList(expectedTemplate);
doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "testPath");
assertNotNull(result);
assertEquals(expectedTemplate, result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_ReturnsNullWhenNoTemplatesFound() {
VMTemplateVO template = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(template);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
doReturn(Collections.emptyList()).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "testPath");
assertNull(result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_NullHypervisor() {
VMTemplateVO expectedTemplate = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(expectedTemplate);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
List<VMTemplateVO> templates = Collections.singletonList(expectedTemplate);
doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
null, CPU.CPUArch.amd64, "testPath");
assertNotNull(result);
assertEquals(expectedTemplate, result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_NullArch() {
VMTemplateVO expectedTemplate = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(expectedTemplate);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
List<VMTemplateVO> templates = Collections.singletonList(expectedTemplate);
doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, null, "testPath");
assertNotNull(result);
assertEquals(expectedTemplate, result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_EmptyUrlPathSuffix() {
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "");
assertNull(result);
}
}

View File

@ -27,6 +27,7 @@ function getTemplateVersion() {
export CS_VERSION="${subversion1}"."${subversion2}"
export CS_MINOR_VERSION="${minorversion}"
export VERSION="${CS_VERSION}.${CS_MINOR_VERSION}"
export CS_SYSTEMTEMPLATE_REPO="https://download.cloudstack.org/systemvm/"
}
function getGenericName() {
@ -63,7 +64,7 @@ function getChecksum() {
function createMetadataFile() {
local fileData=$(cat "$SOURCEFILE")
echo -e "["default"]\nversion = $VERSION.${securityversion}\n" >> "$METADATAFILE"
echo -e "["default"]\nversion = $VERSION.${securityversion}\ndownloadrepository = $CS_SYSTEMTEMPLATE_REPO\n" >> "$METADATAFILE"
for template in "${templates[@]}"
do
section="${template%%:*}"
@ -82,13 +83,21 @@ function createMetadataFile() {
declare -a templates
getTemplateVersion $1
templates=( "kvm-x86_64:https://download.cloudstack.org/systemvm/${CS_VERSION}/systemvmtemplate-$VERSION-x86_64-kvm.qcow2.bz2"
"kvm-aarch64:https://download.cloudstack.org/systemvm/${CS_VERSION}/systemvmtemplate-$VERSION-aarch64-kvm.qcow2.bz2"
"vmware:https://download.cloudstack.org/systemvm/${CS_VERSION}/systemvmtemplate-$VERSION-x86_64-vmware.ova"
"xenserver:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-xen.vhd.bz2"
"hyperv:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-hyperv.vhd.zip"
"lxc:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-kvm.qcow2.bz2"
"ovm3:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-ovm.raw.bz2" )
declare -A template_specs=(
[kvm-x86_64]="x86_64-kvm.qcow2.bz2"
[kvm-aarch64]="aarch64-kvm.qcow2.bz2"
[vmware]="x86_64-vmware.ova"
[xenserver]="x86_64-xen.vhd.bz2"
[hyperv]="x86_64-hyperv.vhd.zip"
[lxc]="x86_64-kvm.qcow2.bz2"
[ovm3]="x86_64-ovm.raw.bz2"
)
templates=()
for key in "${!template_specs[@]}"; do
url="${CS_SYSTEMTEMPLATE_REPO}/${CS_VERSION}/systemvmtemplate-$VERSION-${template_specs[$key]}"
templates+=("$key:$url")
done
PARENTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )/dist/systemvm-templates/"
mkdir -p "$PARENTPATH"

View File

@ -56,10 +56,6 @@ import java.util.stream.Stream;
import javax.inject.Inject;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.resource.ResourceManager;
import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
@ -189,9 +185,11 @@ import com.cloud.configuration.Resource.ResourceType;
import com.cloud.cpu.CPU;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.VsphereStoragePolicyVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.dc.dao.VsphereStoragePolicyDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
@ -218,6 +216,7 @@ import com.cloud.offering.DiskOffering;
import com.cloud.offering.ServiceOffering;
import com.cloud.org.Grouping;
import com.cloud.org.Grouping.AllocationState;
import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceState;
import com.cloud.server.ConfigurationServer;
import com.cloud.server.ManagementServer;
@ -230,6 +229,7 @@ import com.cloud.storage.Volume.Type;
import com.cloud.storage.dao.BucketDao;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.StoragePoolTagsDao;
import com.cloud.storage.dao.StoragePoolWorkDao;
@ -4008,7 +4008,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
return;
}
String templateName = getValidTemplateName(zoneId, hypervisorType);
VMTemplateVO registeredTemplate = systemVmTemplateRegistration.getRegisteredTemplate(templateName, arch);
VMTemplateVO registeredTemplate = systemVmTemplateRegistration.getRegisteredTemplate(templateName,
hypervisorType, arch, url);
TemplateDataStoreVO templateDataStoreVO = null;
if (registeredTemplate != null) {
templateDataStoreVO = _templateStoreDao.findByStoreTemplate(store.getId(), registeredTemplate.getId());
@ -4024,56 +4025,57 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
}
}
SystemVmTemplateRegistration.mountStore(storeUrlAndId.first(), filePath, nfsVersion);
if (templateDataStoreVO != null) {
systemVmTemplateRegistration.validateAndRegisterTemplate(hypervisorType, templateName,
storeUrlAndId.second(), registeredTemplate, templateDataStoreVO, filePath);
if (registeredTemplate != null) {
systemVmTemplateRegistration.validateAndAddTemplateToStore(registeredTemplate, templateDataStoreVO, zoneId,
storeUrlAndId.second(), filePath);
} else {
systemVmTemplateRegistration.validateAndRegisterTemplateForNonExistingEntries(hypervisorType, arch,
templateName, storeUrlAndId, filePath);
systemVmTemplateRegistration.validateAndRegisterNewTemplate(hypervisorType, arch, templateName, zoneId,
storeUrlAndId.second(), filePath);
}
}
private void registerSystemVmTemplateOnFirstNfsStore(Long zoneId, String providerName, String url, DataStore store) {
if (DataStoreProvider.NFS_IMAGE.equals(providerName) && zoneId != null) {
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(final TransactionStatus status) {
List<ImageStoreVO> stores = _imageStoreDao.listAllStoresInZoneExceptId(zoneId, providerName,
DataStoreRole.Image, store.getId());
if (CollectionUtils.isEmpty(stores)) {
List<Pair<HypervisorType, CPU.CPUArch>> hypervisorTypes =
_clusterDao.listDistinctHypervisorsAndArchExcludingExternalType(zoneId);
TransactionLegacy txn = TransactionLegacy.open("AutomaticTemplateRegister");
SystemVmTemplateRegistration systemVmTemplateRegistration = new SystemVmTemplateRegistration();
String filePath = null;
try {
filePath = Files.createTempDirectory(SystemVmTemplateRegistration.TEMPORARY_SECONDARY_STORE).toString();
if (filePath == null) {
throw new CloudRuntimeException("Failed to create temporary file path to mount the store");
if (zoneId == null || !DataStoreProvider.NFS_IMAGE.equals(providerName)) {
logger.debug("Skipping system VM template registration as either zoneId is null or {} " +
"provider is not NFS", store);
return;
}
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(final TransactionStatus status) {
List<ImageStoreVO> stores = _imageStoreDao.listAllStoresInZoneExceptId(zoneId, providerName,
DataStoreRole.Image, store.getId());
if (CollectionUtils.isEmpty(stores)) {
List<Pair<HypervisorType, CPU.CPUArch>> hypervisorArchTypes =
_clusterDao.listDistinctHypervisorsAndArchExcludingExternalType(zoneId);
TransactionLegacy txn = TransactionLegacy.open("AutomaticTemplateRegister");
SystemVmTemplateRegistration systemVmTemplateRegistration = new SystemVmTemplateRegistration();
String filePath = null;
try {
filePath = Files.createTempDirectory(SystemVmTemplateRegistration.TEMPORARY_SECONDARY_STORE)
.toString();
Pair<String, Long> storeUrlAndId = new Pair<>(url, store.getId());
String nfsVersion = imageStoreDetailsUtil.getNfsVersion(store.getId());
for (Pair<HypervisorType, CPU.CPUArch> hypervisorArchType : hypervisorArchTypes) {
try {
registerSystemVmTemplateForHypervisorArch(hypervisorArchType.first(),
hypervisorArchType.second(), zoneId, url, store,
systemVmTemplateRegistration, filePath, storeUrlAndId, nfsVersion);
} catch (CloudRuntimeException e) {
SystemVmTemplateRegistration.unmountStore(filePath);
logger.error("Failed to register system VM template for hypervisor: {} {}",
hypervisorArchType.first().name(), hypervisorArchType.second().name(), e);
}
Pair<String, Long> storeUrlAndId = new Pair<>(url, store.getId());
String nfsVersion = imageStoreDetailsUtil.getNfsVersion(store.getId());
for (Pair<HypervisorType, CPU.CPUArch> hypervisorArchType : hypervisorTypes) {
try {
registerSystemVmTemplateForHypervisorArch(hypervisorArchType.first(),
hypervisorArchType.second(), zoneId, url, store,
systemVmTemplateRegistration, filePath, storeUrlAndId, nfsVersion);
} catch (CloudRuntimeException e) {
SystemVmTemplateRegistration.unmountStore(filePath);
logger.error("Failed to register system VM template for hypervisor: {} {}",
hypervisorArchType.first().name(), hypervisorArchType.second().name(), e);
}
}
} catch (Exception e) {
logger.error("Failed to register systemVM template(s) due to: ", e);
} finally {
SystemVmTemplateRegistration.unmountStore(filePath);
txn.close();
}
} catch (Exception e) {
logger.error("Failed to register systemVM template(s) due to: ", e);
} finally {
SystemVmTemplateRegistration.unmountStore(filePath);
txn.close();
}
}
});
}
}
});
}
@Override
public ImageStore migrateToObjectStore(String name, String url, String providerName, Map<String, String> details) throws DiscoveryException, InvalidParameterValueException {

View File

@ -0,0 +1,58 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.utils.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloud.utils.PropertiesUtil;
public class ServerPropertiesUtil {
private static final Logger logger = LoggerFactory.getLogger(ServerPropertiesUtil.class);
protected static final String PROPERTIES_FILE = "server.properties";
protected static final AtomicReference<Properties> propertiesRef = new AtomicReference<>();
public static String getProperty(String name) {
Properties props = propertiesRef.get();
if (props != null) {
return props.getProperty(name);
}
File propsFile = PropertiesUtil.findConfigFile(PROPERTIES_FILE);
if (propsFile == null) {
logger.error("{} file not found", PROPERTIES_FILE);
return null;
}
Properties tempProps = new Properties();
try (FileInputStream is = new FileInputStream(propsFile)) {
tempProps.load(is);
} catch (IOException e) {
logger.error("Error loading {}: {}", PROPERTIES_FILE, e.getMessage(), e);
return null;
}
if (!propertiesRef.compareAndSet(null, tempProps)) {
tempProps = propertiesRef.get();
}
return tempProps.getProperty(name);
}
}

View File

@ -0,0 +1,95 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.utils.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Properties;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.utils.PropertiesUtil;
@RunWith(MockitoJUnitRunner.class)
public class ServerPropertiesUtilTest {
@After
public void clearCache() {
ServerPropertiesUtil.propertiesRef.set(null);
}
@Test
public void returnsPropertyValueWhenPropertiesAreLoaded() {
Properties mockProperties = mock(Properties.class);
when(mockProperties.getProperty("key")).thenReturn("value");
ServerPropertiesUtil.propertiesRef.set(mockProperties);
String result = ServerPropertiesUtil.getProperty("key");
assertEquals("value", result);
}
@Test
public void returnsNullWhenPropertyDoesNotExist() {
Properties mockProperties = mock(Properties.class);
ServerPropertiesUtil.propertiesRef.set(mockProperties);
assertNull(ServerPropertiesUtil.getProperty("nonexistentKey"));
}
@Test
public void loadsPropertiesFromFileWhenNotCached() throws Exception {
File tempFile = Files.createTempFile("server", ".properties").toFile();
tempFile.deleteOnExit();
Files.writeString(tempFile.toPath(), "key=value\n");
try (MockedStatic<PropertiesUtil> mocked = mockStatic(PropertiesUtil.class)) {
mocked.when(() -> PropertiesUtil.findConfigFile(ServerPropertiesUtil.PROPERTIES_FILE))
.thenReturn(tempFile);
assertEquals("value", ServerPropertiesUtil.getProperty("key"));
}
}
@Test
public void returnsNullWhenPropertiesFileNotFound() {
try (MockedStatic<PropertiesUtil> mocked = mockStatic(PropertiesUtil.class)) {
mocked.when(() -> PropertiesUtil.findConfigFile(ServerPropertiesUtil.PROPERTIES_FILE))
.thenReturn(null);
assertNull(ServerPropertiesUtil.getProperty("key"));
}
}
@Test
public void returnsNullWhenIOExceptionOccurs() throws IOException {
File tempFile = Files.createTempFile("bad", ".properties").toFile();
tempFile.deleteOnExit();
Files.writeString(tempFile.toPath(), "\u0000\u0000\u0000");
try (MockedStatic<PropertiesUtil> mocked = mockStatic(PropertiesUtil.class)) {
mocked.when(() -> PropertiesUtil.findConfigFile(ServerPropertiesUtil.PROPERTIES_FILE))
.thenReturn(tempFile);
assertNull(ServerPropertiesUtil.getProperty("key"));
}
}
}