Skip to content

Commit 34b8870

Browse files
authored
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>
1 parent 5bf869c commit 34b8870

File tree

12 files changed

+2385
-534
lines changed

12 files changed

+2385
-534
lines changed

client/bindir/cloud-setup-management.in

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,106 @@ from cloudutils.cloudException import CloudRuntimeException, CloudInternalExcept
3636
from cloudutils.globalEnv import globalEnv
3737
from cloudutils.serviceConfigServer import cloudManagementConfig
3838
from optparse import OptionParser
39+
import urllib.request
40+
import configparser
41+
import hashlib
42+
43+
SYSTEMVM_TEMPLATES_PATH = "/usr/share/cloudstack-management/templates/systemvm"
44+
SYSTEMVM_TEMPLATES_METADATA_FILE = SYSTEMVM_TEMPLATES_PATH + "/metadata.ini"
45+
46+
def verify_sha512_checksum(file_path, expected_checksum):
47+
sha512 = hashlib.sha512()
48+
try:
49+
with open(file_path, "rb") as f:
50+
for chunk in iter(lambda: f.read(8192), b""):
51+
sha512.update(chunk)
52+
return sha512.hexdigest().lower() == expected_checksum.lower()
53+
except Exception as e:
54+
print(f"Failed to verify checksum for {file_path}: {e}")
55+
return False
56+
57+
def download_file(url, dest_path, chunk_size=8 * 1024 * 1024):
58+
"""
59+
Downloads a file from the given URL to the specified destination path in chunks.
60+
"""
61+
try:
62+
with urllib.request.urlopen(url) as response:
63+
total_size = response.length if response.length else None
64+
downloaded = 0
65+
try:
66+
with open(dest_path, 'wb') as out_file:
67+
while True:
68+
chunk = response.read(chunk_size)
69+
if not chunk:
70+
break
71+
out_file.write(chunk)
72+
downloaded += len(chunk)
73+
if total_size:
74+
print(f"Downloaded {downloaded / (1024 * 1024):.2f}MB of {total_size / (1024 * 1024):.2f}MB", end='\r')
75+
except PermissionError as pe:
76+
print(f"Permission denied: {dest_path}")
77+
raise
78+
print(f"\nDownloaded file from {url} to {dest_path}")
79+
except Exception as e:
80+
print(f"Failed to download file: {e}")
81+
raise
82+
83+
def download_template_if_needed(template, url, filename, checksum):
84+
dest_path = os.path.join(SYSTEMVM_TEMPLATES_PATH, filename)
85+
if os.path.exists(dest_path):
86+
if checksum and verify_sha512_checksum(dest_path, checksum):
87+
print(f"{template} System VM template already exists at {dest_path} with valid checksum, skipping download.")
88+
return
89+
else:
90+
print(f"{template} System VM template at {dest_path} has invalid or missing checksum, re-downloading...")
91+
else:
92+
print(f"Downloading {template} System VM template from {url} to {dest_path}...")
93+
try:
94+
download_file(url, dest_path)
95+
#After download, verify checksum if provided
96+
if checksum:
97+
if verify_sha512_checksum(dest_path, checksum):
98+
print(f"{template} System VM template downloaded and verified successfully.")
99+
else:
100+
print(f"ERROR: Checksum verification failed for {template} System VM template after download.")
101+
except Exception as e:
102+
print(f"ERROR: Failed to download {template} System VM template: {e}")
103+
104+
def collect_template_metadata(selected_templates, options):
105+
template_metadata_list = []
106+
if not os.path.exists(SYSTEMVM_TEMPLATES_METADATA_FILE):
107+
print(f"ERROR: System VM templates metadata file not found at {SYSTEMVM_TEMPLATES_METADATA_FILE}, cannot download templates.")
108+
sys.exit(1)
109+
config = configparser.ConfigParser()
110+
config.read(SYSTEMVM_TEMPLATES_METADATA_FILE)
111+
template_repo_url = None
112+
if options.systemvm_templates_repository:
113+
if "default" in config and "downloadrepository" in config["default"]:
114+
template_repo_url = config["default"]["downloadrepository"].strip()
115+
if not template_repo_url:
116+
print("ERROR: downloadrepository value is empty in metadata file, cannot use --systemvm-template-repository option.")
117+
sys.exit(1)
118+
for template in selected_templates:
119+
if template in config:
120+
url = config[template].get("downloadurl")
121+
filename = config[template].get("filename")
122+
checksum = config[template].get("checksum")
123+
if url and filename:
124+
if template_repo_url:
125+
url = url.replace(template_repo_url, options.systemvm_templates_repository)
126+
template_metadata_list.append({
127+
"template": template,
128+
"url": url,
129+
"filename": filename,
130+
"checksum": checksum
131+
})
132+
else:
133+
print(f"ERROR: URL or filename not found for {template} System VM template in metadata.")
134+
sys.exit(1)
135+
else:
136+
print(f"ERROR: No metadata found for {template} System VM template.")
137+
sys.exit(1)
138+
return template_metadata_list
39139

40140
if __name__ == '__main__':
41141
initLoging("@MSLOGDIR@/setupManagement.log")
@@ -45,6 +145,16 @@ if __name__ == '__main__':
45145
parser.add_option("--https", action="store_true", dest="https", help="Enable HTTPs connection of management server")
46146
parser.add_option("--tomcat7", action="store_true", dest="tomcat7", help="Depreciated option, don't use it")
47147
parser.add_option("--no-start", action="store_true", dest="nostart", help="Do not start management server after successful configuration")
148+
parser.add_option(
149+
"--systemvm-templates",
150+
dest="systemvm_templates",
151+
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.",
152+
)
153+
parser.add_option(
154+
"--systemvm-templates-repository",
155+
dest="systemvm_templates_repository",
156+
help="Specify the URL to download System VM templates from."
157+
)
48158
(options, args) = parser.parse_args()
49159
if options.https:
50160
glbEnv.svrMode = "HttpsServer"
@@ -53,6 +163,34 @@ if __name__ == '__main__':
53163
if options.nostart:
54164
glbEnv.noStart = True
55165

166+
available_templates = ["kvm-aarch64", "kvm-x86_64", "xenserver", "vmware"]
167+
templates_arg = options.systemvm_templates
168+
169+
selected_templates = ["kvm-x86_64"]
170+
if templates_arg:
171+
templates_list = [t.strip().lower() for t in templates_arg.split(",")]
172+
if "all" in templates_list:
173+
if len(templates_list) > 1:
174+
print("WARNING: 'all' specified for System VM templates, ignoring other specified templates.")
175+
selected_templates = available_templates
176+
else:
177+
invalid_templates = []
178+
for t in templates_list:
179+
if t in available_templates:
180+
if t not in selected_templates:
181+
selected_templates.append(t)
182+
else:
183+
if t not in invalid_templates:
184+
invalid_templates.append(t)
185+
if invalid_templates:
186+
print(f"ERROR: Invalid System VM template names provided: {', '.join(invalid_templates)}")
187+
sys.exit(1)
188+
print(f"Selected systemvm templates to download: {', '.join(selected_templates) if selected_templates else 'None'}")
189+
190+
template_metadata_list = []
191+
if selected_templates:
192+
template_metadata_list = collect_template_metadata(selected_templates, options)
193+
56194
glbEnv.mode = "Server"
57195

58196
print("Starting to configure CloudStack Management Server:")
@@ -74,3 +212,6 @@ if __name__ == '__main__':
74212
syscfg.restore()
75213
except:
76214
pass
215+
216+
for meta in template_metadata_list:
217+
download_template_if_needed(meta["template"], meta["url"], meta["filename"], meta["checksum"])

client/conf/server.properties.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,8 @@ extensions.deployment.mode=@EXTENSIONSDEPLOYMENTMODE@
6262
# Thread pool configuration
6363
#threads.min=10
6464
#threads.max=500
65+
66+
# The URL prefix for the system VM templates repository. When downloading system VM templates, the server replaces the
67+
# `downloadrepository` key value from the metadata file in template URLs. If not specified, the original template URL
68+
# will be used for download.
69+
# system.vm.templates.download.repository=http://download.cloudstack.org/systemvm/

engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public class DataCenterDetailsDaoImpl extends ResourceDetailsDaoBase<DataCenterD
3131

3232
private final SearchBuilder<DataCenterDetailVO> DetailSearch;
3333

34-
DataCenterDetailsDaoImpl() {
34+
public DataCenterDetailsDaoImpl() {
35+
super();
3536
DetailSearch = createSearchBuilder();
3637
DetailSearch.and("zoneId", DetailSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
3738
DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ);

engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
9494

9595
List<VMTemplateVO> listByParentTemplatetId(long parentTemplatetId);
9696

97-
VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch);
97+
VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch);
9898

9999
List<VMTemplateVO> findTemplatesLinkedToUserdata(long userdataId);
100100

@@ -103,4 +103,7 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
103103
List<Long> listIdsByTemplateTag(String tag);
104104

105105
List<Long> listIdsByExtensionId(long extensionId);
106+
107+
VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
108+
CPU.CPUArch arch, String urlPathSuffix);
106109
}

engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,17 @@ public List<VMTemplateVO> listReadyTemplates() {
245245

246246

247247
@Override
248-
public VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch) {
248+
public VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch) {
249249
SearchBuilder<VMTemplateVO> sb = createSearchBuilder();
250250
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
251+
sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
251252
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
252253
sb.done();
253254
SearchCriteria<VMTemplateVO> sc = sb.create();
254255
sc.setParameters("name", name);
256+
if (hypervisorType != null) {
257+
sc.setParameters("hypervisorType", hypervisorType);
258+
}
255259
if (arch != null) {
256260
sc.setParameters("arch", arch);
257261
}
@@ -850,6 +854,37 @@ public List<Long> listIdsByExtensionId(long extensionId) {
850854
return customSearch(sc, null);
851855
}
852856

857+
@Override
858+
public VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
859+
CPU.CPUArch arch, String urlPathSuffix) {
860+
if (StringUtils.isBlank(urlPathSuffix)) {
861+
return null;
862+
}
863+
SearchBuilder<VMTemplateVO> sb = createSearchBuilder();
864+
sb.and("templateType", sb.entity().getTemplateType(), SearchCriteria.Op.EQ);
865+
sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
866+
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
867+
sb.and("urlPathSuffix", sb.entity().getUrl(), SearchCriteria.Op.LIKE);
868+
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
869+
sb.done();
870+
SearchCriteria<VMTemplateVO> sc = sb.create();
871+
sc.setParameters("templateType", TemplateType.SYSTEM);
872+
if (hypervisorType != null) {
873+
sc.setParameters("hypervisorType", hypervisorType);
874+
}
875+
if (arch != null) {
876+
sc.setParameters("arch", arch);
877+
}
878+
sc.setParameters("urlPathSuffix", "%" + urlPathSuffix);
879+
sc.setParameters("state", VirtualMachineTemplate.State.Active);
880+
Filter filter = new Filter(VMTemplateVO.class, "id", false, null, 1L);
881+
List<VMTemplateVO> templates = listBy(sc, filter);
882+
if (CollectionUtils.isNotEmpty(templates)) {
883+
return templates.get(0);
884+
}
885+
return null;
886+
}
887+
853888
@Override
854889
public boolean updateState(
855890
com.cloud.template.VirtualMachineTemplate.State currentState,

0 commit comments

Comments
 (0)