Skip to content

Commit e86bb41

Browse files
nvazquezyadvr
authored andcommitted
CLOUDSTACK-10146: Bypass Secondary Storage for KVM templates (#2379)
This feature allows using templates and ISOs avoiding secondary storage as intermediate cache on KVM. The virtual machine deployment process is enhanced to supported bypassed registered templates and ISOs, delegating the work of downloading them to primary storage to the KVM agent instead of the SSVM agent. Template and ISO registration: - When hypervisor is KVM, a checkbox is displayed with 'Direct Download' label. - API methods registerTemplate and registerISO are both extended with this new parameter directdownload. - On template or ISO registration, no download job is sent to SSVM agent, CloudStack would only persist an entry on template_store_ref indicating that template or ISO has been marked as 'Direct Download' (bypassing Secondary Storage). These entries are persisted as: template_id = Template or ISO id on vm_template table store_id NULL download_state = BYPASSED state = Ready (Note: these entries allow users to deploy virtual machine from registered templates or ISOs) - An URL validation command is sent to a random KVM host to check if template/ISO location can be reached. Metalink are also supported by this feature. In case of a metalink, it is fetched and URL check is performed on each of its URLs. - Checksum should be provided as indicated on #2246: {ALGORITHM}CHKSUMHASH - After template or ISO is registered, it would be displayed in the UI Virtual machine deployment: When a 'Direct Download' template is selected for deployment, CloudStack would delegate template downloading to destination storage pool via destination host by a new pluggable download manager. Download manager would handle template downloading depending on URL protocol. In case of HTTP, request headers can be set by the user via vm_template_details. Those details should be persisted as: Key: HTTP_HEADER Value: HEADERNAME:HEADERVALUE In case of HTTPS, a new API method is added uploadTemplateDirectDownloadCertificate to allow user importing a client certificate into all KVM hosts' keystore before deployment. After template or ISO is downloaded to primary storage, usual entry would be persisted on template_spool_ref indicating the mapping between template/ISO and storage pool.
1 parent 0d0fa5e commit e86bb41

File tree

103 files changed

+2726
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+2726
-103
lines changed

agent/src/com/cloud/agent/Agent.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import javax.naming.ConfigurationException;
3939

40+
import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate;
4041
import org.apache.cloudstack.ca.SetupCertificateAnswer;
4142
import org.apache.cloudstack.ca.SetupCertificateCommand;
4243
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
@@ -551,6 +552,8 @@ protected void processRequest(final Request request, final Link link) {
551552
answer = setupAgentKeystore((SetupKeyStoreCommand) cmd);
552553
} else if (cmd instanceof SetupCertificateCommand && ((SetupCertificateCommand) cmd).isHandleByAgent()) {
553554
answer = setupAgentCertificate((SetupCertificateCommand) cmd);
555+
} else if (cmd instanceof SetupDirectDownloadCertificate) {
556+
answer = setupDirectDownloadCertificate((SetupDirectDownloadCertificate) cmd);
554557
} else {
555558
if (cmd instanceof ReadyCommand) {
556559
processReadyCommand(cmd);
@@ -600,6 +603,31 @@ protected void processRequest(final Request request, final Link link) {
600603
}
601604
}
602605

606+
private Answer setupDirectDownloadCertificate(SetupDirectDownloadCertificate cmd) {
607+
String certificate = cmd.getCertificate();
608+
String certificateName = cmd.getCertificateName();
609+
s_logger.info("Importing certificate " + certificateName + " into keystore");
610+
611+
final File agentFile = PropertiesUtil.findConfigFile("agent.properties");
612+
if (agentFile == null) {
613+
return new Answer(cmd, false, "Failed to find agent.properties file");
614+
}
615+
616+
final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
617+
618+
String cerFile = agentFile.getParent() + "/" + certificateName + ".cer";
619+
Script.runSimpleBashScript(String.format("echo '%s' > %s", certificate, cerFile));
620+
621+
String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null";
622+
String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath());
623+
String privatePassword = Script.runSimpleBashScript(privatePasswordCmd);
624+
625+
String importCommandFormat = "keytool -importcert -file %s -keystore %s -alias '%s' -storepass '%s' -noprompt";
626+
String importCmd = String.format(importCommandFormat, cerFile, keyStoreFile, certificateName, privatePassword);
627+
Script.runSimpleBashScript(importCmd);
628+
return new Answer(cmd, true, "Certificate " + certificateName + " imported");
629+
}
630+
603631
public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) {
604632
final String keyStorePassword = cmd.getKeystorePassword();
605633
final long validityDays = cmd.getValidityDays();
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.direct.download;
21+
22+
public interface DirectTemplateDownloader {
23+
24+
class DirectTemplateInformation {
25+
private String installPath;
26+
private Long size;
27+
private String checksum;
28+
29+
public DirectTemplateInformation(String installPath, Long size, String checksum) {
30+
this.installPath = installPath;
31+
this.size = size;
32+
this.checksum = checksum;
33+
}
34+
35+
public String getInstallPath() {
36+
return installPath;
37+
}
38+
39+
public Long getSize() {
40+
return size;
41+
}
42+
43+
public String getChecksum() {
44+
return checksum;
45+
}
46+
}
47+
48+
/**
49+
* Perform template download to pool specified on downloader creation
50+
* @return true if successful, false if not
51+
*/
52+
boolean downloadTemplate();
53+
54+
/**
55+
* Perform extraction (if necessary) and installation of previously downloaded template
56+
* @return true if successful, false if not
57+
*/
58+
boolean extractAndInstallDownloadedTemplate();
59+
60+
/**
61+
* Get template information after it is properly installed on pool
62+
* @return template information
63+
*/
64+
DirectTemplateInformation getTemplateInformation();
65+
66+
/**
67+
* Perform checksum validation of previously downloadeed template
68+
* @return true if successful, false if not
69+
*/
70+
boolean validateChecksum();
71+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
package com.cloud.agent.direct.download;
20+
21+
import com.cloud.utils.exception.CloudRuntimeException;
22+
import com.cloud.utils.script.Script;
23+
import org.apache.cloudstack.utils.security.ChecksumValue;
24+
import org.apache.commons.lang.StringUtils;
25+
26+
import java.io.File;
27+
import java.util.UUID;
28+
29+
public abstract class DirectTemplateDownloaderImpl implements DirectTemplateDownloader {
30+
31+
private String url;
32+
private String destPoolPath;
33+
private Long templateId;
34+
private String downloadedFilePath;
35+
private String installPath;
36+
private String checksum;
37+
38+
protected DirectTemplateDownloaderImpl(final String url, final String destPoolPath, final Long templateId, final String checksum) {
39+
this.url = url;
40+
this.destPoolPath = destPoolPath;
41+
this.templateId = templateId;
42+
this.checksum = checksum;
43+
}
44+
45+
private static String directDownloadDir = "template";
46+
47+
/**
48+
* Return direct download temporary path to download template
49+
*/
50+
protected static String getDirectDownloadTempPath(Long templateId) {
51+
String templateIdAsString = String.valueOf(templateId);
52+
return directDownloadDir + File.separator + templateIdAsString.substring(0,1) +
53+
File.separator + templateIdAsString;
54+
}
55+
56+
/**
57+
* Create folder on path if it does not exist
58+
*/
59+
protected void createFolder(String path) {
60+
File dir = new File(path);
61+
if (!dir.exists()) {
62+
dir.mkdirs();
63+
}
64+
}
65+
66+
public String getUrl() {
67+
return url;
68+
}
69+
70+
public String getDestPoolPath() {
71+
return destPoolPath;
72+
}
73+
74+
public Long getTemplateId() {
75+
return templateId;
76+
}
77+
78+
public String getDownloadedFilePath() {
79+
return downloadedFilePath;
80+
}
81+
82+
public void setDownloadedFilePath(String filePath) {
83+
this.downloadedFilePath = filePath;
84+
}
85+
86+
/**
87+
* Return filename from url
88+
*/
89+
public String getFileNameFromUrl() {
90+
String[] urlParts = url.split("/");
91+
return urlParts[urlParts.length - 1];
92+
}
93+
94+
/**
95+
* Checks if downloaded template is extractable
96+
* @return true if it should be extracted, false if not
97+
*/
98+
private boolean isTemplateExtractable() {
99+
String type = Script.runSimpleBashScript("file " + downloadedFilePath + " | awk -F' ' '{print $2}'");
100+
return type.equalsIgnoreCase("bzip2") || type.equalsIgnoreCase("gzip") || type.equalsIgnoreCase("zip");
101+
}
102+
103+
@Override
104+
public boolean extractAndInstallDownloadedTemplate() {
105+
installPath = UUID.randomUUID().toString();
106+
if (isTemplateExtractable()) {
107+
extractDownloadedTemplate();
108+
} else {
109+
Script.runSimpleBashScript("mv " + downloadedFilePath + " " + getInstallFullPath());
110+
}
111+
return true;
112+
}
113+
114+
/**
115+
* Return install full path
116+
*/
117+
private String getInstallFullPath() {
118+
return destPoolPath + File.separator + installPath;
119+
}
120+
121+
/**
122+
* Return extract command to execute given downloaded file
123+
*/
124+
private String getExtractCommandForDownloadedFile() {
125+
if (downloadedFilePath.endsWith(".zip")) {
126+
return "unzip -p " + downloadedFilePath + " | cat > " + getInstallFullPath();
127+
} else if (downloadedFilePath.endsWith(".bz2")) {
128+
return "bunzip2 -c " + downloadedFilePath + " > " + getInstallFullPath();
129+
} else if (downloadedFilePath.endsWith(".gz")) {
130+
return "gunzip -c " + downloadedFilePath + " > " + getInstallFullPath();
131+
} else {
132+
throw new CloudRuntimeException("Unable to extract template " + templateId + " on " + downloadedFilePath);
133+
}
134+
}
135+
136+
/**
137+
* Extract downloaded template into installPath, remove compressed file
138+
*/
139+
private void extractDownloadedTemplate() {
140+
String extractCommand = getExtractCommandForDownloadedFile();
141+
Script.runSimpleBashScript(extractCommand);
142+
Script.runSimpleBashScript("rm -f " + downloadedFilePath);
143+
}
144+
145+
@Override
146+
public DirectTemplateInformation getTemplateInformation() {
147+
String sizeResult = Script.runSimpleBashScript("ls -als " + getInstallFullPath() + " | awk '{print $1}'");
148+
long size = Long.parseLong(sizeResult);
149+
return new DirectTemplateInformation(installPath, size, checksum);
150+
}
151+
152+
/**
153+
* Return checksum command from algorithm
154+
*/
155+
private String getChecksumCommandFromAlgorithm(String algorithm) {
156+
if (algorithm.equalsIgnoreCase("MD5")) {
157+
return "md5sum";
158+
} else if (algorithm.equalsIgnoreCase("SHA-1")) {
159+
return "sha1sum";
160+
} else if (algorithm.equalsIgnoreCase("SHA-224")) {
161+
return "sha224sum";
162+
} else if (algorithm.equalsIgnoreCase("SHA-256")) {
163+
return "sha256sum";
164+
} else if (algorithm.equalsIgnoreCase("SHA-384")) {
165+
return "sha384sum";
166+
} else if (algorithm.equalsIgnoreCase("SHA-512")) {
167+
return "sha512sum";
168+
} else {
169+
throw new CloudRuntimeException("Unknown checksum algorithm: " + algorithm);
170+
}
171+
}
172+
173+
@Override
174+
public boolean validateChecksum() {
175+
if (StringUtils.isNotBlank(checksum)) {
176+
ChecksumValue providedChecksum = new ChecksumValue(checksum);
177+
String algorithm = providedChecksum.getAlgorithm();
178+
String checksumCommand = "echo '%s %s' | %s -c --quiet";
179+
String cmd = String.format(checksumCommand, providedChecksum.getChecksum(), downloadedFilePath, getChecksumCommandFromAlgorithm(algorithm));
180+
int result = Script.runSimpleBashScriptForExitValue(cmd);
181+
return result == 0;
182+
}
183+
return true;
184+
}
185+
}

0 commit comments

Comments
 (0)