mirror of https://github.com/apache/cloudstack.git
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:
parent
5bf869c803
commit
34b8870f59
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue