This is an automated email from the ASF dual-hosted git repository.
nvazquez pushed a commit to branch 4.11
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.11 by this push:
new 12c850e KVM: Improvements on upload direct download certificates
(#2995)
12c850e is described below
commit 12c850ed2f3893313a6b31de33f47857705d9b4b
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Tue Jun 4 03:08:31 2019 -0300
KVM: Improvements on upload direct download certificates (#2995)
* Improvements on upload direct download certificates
* Move upload direct download certificate logic to KVM plugin
* Extend unit test certificate expiration days
* Add marvin tests and command to revoke certificates
* Review comments
* Do not include revoke certificates API
---
agent/src/com/cloud/agent/Agent.java | 28 ---
...ploadTemplateDirectDownloadCertificateCmd.java} | 6 +-
... => SetupDirectDownloadCertificateCommand.java} | 4 +-
...tupDirectDownloadCertificateCommandWrapper.java | 136 ++++++++++++
.../src/com/cloud/server/ManagementServerImpl.java | 4 +-
.../direct/download/DirectDownloadManagerImpl.java | 76 ++++++-
.../download/DirectDownloadManagerImplTest.java | 33 +++
test/integration/smoke/test_direct_download.py | 227 +++++++++++++++++++++
8 files changed, 472 insertions(+), 42 deletions(-)
diff --git a/agent/src/com/cloud/agent/Agent.java
b/agent/src/com/cloud/agent/Agent.java
index 500724d..e5fbdd7 100644
--- a/agent/src/com/cloud/agent/Agent.java
+++ b/agent/src/com/cloud/agent/Agent.java
@@ -39,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.ConfigurationException;
-import
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate;
import org.apache.cloudstack.agent.lb.SetupMSListAnswer;
import org.apache.cloudstack.agent.lb.SetupMSListCommand;
import org.apache.cloudstack.ca.PostCertificateRenewalCommand;
@@ -630,8 +629,6 @@ public class Agent implements HandlerFactory, IAgentControl
{
if (Host.Type.Routing.equals(_resource.getType())) {
scheduleServicesRestartTask();
}
- } else if (cmd instanceof SetupDirectDownloadCertificate) {
- answer =
setupDirectDownloadCertificate((SetupDirectDownloadCertificate) cmd);
} else if (cmd instanceof SetupMSListCommand) {
answer =
setupManagementServerList((SetupMSListCommand) cmd);
} else {
@@ -683,31 +680,6 @@ public class Agent implements HandlerFactory,
IAgentControl {
}
}
- private Answer
setupDirectDownloadCertificate(SetupDirectDownloadCertificate cmd) {
- String certificate = cmd.getCertificate();
- String certificateName = cmd.getCertificateName();
- s_logger.info("Importing certificate " + certificateName + " into
keystore");
-
- final File agentFile =
PropertiesUtil.findConfigFile("agent.properties");
- if (agentFile == null) {
- return new Answer(cmd, false, "Failed to find agent.properties
file");
- }
-
- final String keyStoreFile = agentFile.getParent() + "/" +
KeyStoreUtils.KS_FILENAME;
-
- String cerFile = agentFile.getParent() + "/" + certificateName +
".cer";
- Script.runSimpleBashScript(String.format("echo '%s' > %s",
certificate, cerFile));
-
- String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s'
2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null";
- String privatePasswordCmd = String.format(privatePasswordFormat,
agentFile.getAbsolutePath());
- String privatePassword =
Script.runSimpleBashScript(privatePasswordCmd);
-
- String importCommandFormat = "keytool -importcert -file %s -keystore
%s -alias '%s' -storepass '%s' -noprompt";
- String importCmd = String.format(importCommandFormat, cerFile,
keyStoreFile, certificateName, privatePassword);
- Script.runSimpleBashScript(importCmd);
- return new Answer(cmd, true, "Certificate " + certificateName + "
imported");
- }
-
public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) {
final String keyStorePassword = cmd.getKeystorePassword();
final long validityDays = cmd.getValidityDays();
diff --git
a/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java
b/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
similarity index 93%
rename from
api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java
rename to
api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
index 89c0c25..416d264 100755
---
a/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java
+++
b/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
@@ -35,19 +35,19 @@ import org.apache.log4j.Logger;
import javax.inject.Inject;
-@APICommand(name = UploadTemplateDirectDownloadCertificate.APINAME,
+@APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME,
description = "Upload a certificate for HTTPS direct template download
on KVM hosts",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = true,
responseHasSensitiveInfo = true,
since = "4.11.0",
authorized = {RoleType.Admin})
-public class UploadTemplateDirectDownloadCertificate extends BaseCmd {
+public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
@Inject
DirectDownloadManager directDownloadManager;
- private static final Logger LOG =
Logger.getLogger(UploadTemplateDirectDownloadCertificate.class);
+ private static final Logger LOG =
Logger.getLogger(UploadTemplateDirectDownloadCertificateCmd.class);
public static final String APINAME =
"uploadTemplateDirectDownloadCertificate";
@Parameter(name = ApiConstants.CERTIFICATE, type =
BaseCmd.CommandType.STRING, required = true, length = 65535,
diff --git
a/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificate.java
b/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificateCommand.java
similarity index 89%
rename from
core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificate.java
rename to
core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificateCommand.java
index 836b321..641c535 100644
---
a/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificate.java
+++
b/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificateCommand.java
@@ -20,12 +20,12 @@ package org.apache.cloudstack.agent.directdownload;
import com.cloud.agent.api.Command;
-public class SetupDirectDownloadCertificate extends Command {
+public class SetupDirectDownloadCertificateCommand extends Command {
private String certificate;
private String certificateName;
- public SetupDirectDownloadCertificate(String certificate, String name) {
+ public SetupDirectDownloadCertificateCommand(String certificate, String
name) {
this.certificate = certificate;
this.certificateName = name;
}
diff --git
a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
new file mode 100644
index 0000000..97035ee
--- /dev/null
+++
b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
@@ -0,0 +1,136 @@
+//
+// 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 com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
+import
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.log4j.Logger;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+@ResourceWrapper(handles = SetupDirectDownloadCertificateCommand.class)
+public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends
CommandWrapper<SetupDirectDownloadCertificateCommand, Answer,
LibvirtComputingResource> {
+
+ private static final String temporaryCertFilePrefix = "CSCERTIFICATE";
+
+ private static final Logger s_logger =
Logger.getLogger(LibvirtSetupDirectDownloadCertificateCommandWrapper.class);
+
+ /**
+ * Retrieve agent.properties file
+ */
+ private File getAgentPropertiesFile() throws FileNotFoundException {
+ final File agentFile =
PropertiesUtil.findConfigFile("agent.properties");
+ if (agentFile == null) {
+ throw new FileNotFoundException("Failed to find agent.properties
file");
+ }
+ return agentFile;
+ }
+
+ /**
+ * Get the property 'keystore.passphrase' value from agent.properties file
+ */
+ private String getKeystorePassword(File agentFile) {
+ String pass = null;
+ if (agentFile != null) {
+ try {
+ pass =
PropertiesUtil.loadFromFile(agentFile).getProperty(KeyStoreUtils.KS_PASSPHRASE_PROPERTY);
+ } catch (IOException e) {
+ s_logger.error("Could not get 'keystore.passphrase' property
value due to: " + e.getMessage());
+ }
+ }
+ return pass;
+ }
+
+ /**
+ * Get keystore path
+ */
+ private String getKeyStoreFilePath(File agentFile) {
+ return agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME;
+ }
+
+ /**
+ * Import certificate from temporary file into keystore
+ */
+ private void importCertificate(String tempCerFilePath, String
keyStoreFile, String certificateName, String privatePassword) {
+ s_logger.debug("Importing certificate from temporary file to
keystore");
+ String importCommandFormat = "keytool -importcert -file %s -keystore
%s -alias '%s' -storepass '%s' -noprompt";
+ String importCmd = String.format(importCommandFormat, tempCerFilePath,
keyStoreFile, certificateName, privatePassword);
+ int result = Script.runSimpleBashScriptForExitValue(importCmd);
+ if (result != 0) {
+ s_logger.debug("Certificate " + certificateName + " not imported
as it already exist on keystore");
+ }
+ }
+
+ /**
+ * Create temporary file and return its path
+ */
+ private String createTemporaryFile(File agentFile, String certificateName,
String certificate) {
+ String tempCerFilePath = String.format("%s/%s-%s",
+ agentFile.getParent(), temporaryCertFilePrefix,
certificateName);
+ s_logger.debug("Creating temporary certificate file into: " +
tempCerFilePath);
+ int result =
Script.runSimpleBashScriptForExitValue(String.format("echo '%s' > %s",
certificate, tempCerFilePath));
+ if (result != 0) {
+ throw new CloudRuntimeException("Could not create the certificate
file on path: " + tempCerFilePath);
+ }
+ return tempCerFilePath;
+ }
+
+ /**
+ * Remove temporary file
+ */
+ private void cleanupTemporaryFile(String temporaryFile) {
+ s_logger.debug("Cleaning up temporary certificate file");
+ Script.runSimpleBashScript("rm -f " + temporaryFile);
+ }
+
+ @Override
+ public Answer execute(SetupDirectDownloadCertificateCommand cmd,
LibvirtComputingResource serverResource) {
+ String certificate = cmd.getCertificate();
+ String certificateName = cmd.getCertificateName();
+
+ try {
+ File agentFile = getAgentPropertiesFile();
+ String privatePassword = getKeystorePassword(agentFile);
+ if (isBlank(privatePassword)) {
+ return new Answer(cmd, false, "No password found for keystore:
" + KeyStoreUtils.KS_FILENAME);
+ }
+
+ final String keyStoreFile = getKeyStoreFilePath(agentFile);
+ String temporaryFile = createTemporaryFile(agentFile,
certificateName, certificate);
+ importCertificate(temporaryFile, keyStoreFile, certificateName,
privatePassword);
+ cleanupTemporaryFile(temporaryFile);
+ } catch (FileNotFoundException | CloudRuntimeException e) {
+ s_logger.error("Error while setting up certificate " +
certificateName, e);
+ return new Answer(cmd, false, e.getMessage());
+ }
+
+ return new Answer(cmd, true, "Certificate " + certificateName + "
imported");
+ }
+}
diff --git a/server/src/com/cloud/server/ManagementServerImpl.java
b/server/src/com/cloud/server/ManagementServerImpl.java
index 38a84bb..872f9fd 100644
--- a/server/src/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/com/cloud/server/ManagementServerImpl.java
@@ -65,7 +65,7 @@ import
org.apache.cloudstack.api.command.admin.config.ListDeploymentPlannersCmd;
import
org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import
org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd;
-import
org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificate;
+import
org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.domain.CreateDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.DeleteDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
@@ -3051,7 +3051,7 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
cmdList.add(ReleasePodIpCmdByAdmin.class);
cmdList.add(CreateManagementNetworkIpRangeCmd.class);
cmdList.add(DeleteManagementNetworkIpRangeCmd.class);
- cmdList.add(UploadTemplateDirectDownloadCertificate.class);
+ cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class);
// Out-of-band management APIs for admins
cmdList.add(EnableOutOfBandManagementForHostCmd.class);
diff --git
a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
index c1ffc5e..d2aa675 100755
---
a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
+++
b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
@@ -41,6 +41,10 @@ import com.cloud.utils.exception.CloudRuntimeException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -50,6 +54,7 @@ import java.util.Collections;
import java.util.stream.Collectors;
import javax.inject.Inject;
+import com.cloud.utils.security.CertificateHelper;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
import
org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol;
@@ -57,7 +62,7 @@ import
org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand;
import
org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand;
-import
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate;
+import
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
@@ -69,6 +74,7 @@ import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
+import sun.security.x509.X509CertImpl;
public class DirectDownloadManagerImpl extends ManagerBase implements
DirectDownloadManager {
@@ -313,14 +319,66 @@ public class DirectDownloadManagerImpl extends
ManagerBase implements DirectDown
.collect(Collectors.toList());
}
+ /**
+ * Return pretified PEM certificate
+ */
+ protected String getPretifiedCertificate(String certificateCer) {
+ String cert = certificateCer.replaceAll("(.{64})", "$1\n");
+ if (!cert.startsWith(BEGIN_CERT) && !cert.endsWith(END_CERT)) {
+ cert = BEGIN_CERT + LINE_SEPARATOR + cert + LINE_SEPARATOR +
END_CERT;
+ }
+ return cert;
+ }
+
+ /**
+ * Generate and return certificate from the string
+ * @throws CloudRuntimeException if the certificate is not well formed
+ */
+ private Certificate getCertificateFromString(String certificatePem) {
+ try {
+ return CertificateHelper.buildCertificate(certificatePem);
+ } catch (CertificateException e) {
+ e.printStackTrace();
+ throw new CloudRuntimeException("Cannot parse the certificate
provided, please provide a PEM certificate. Error: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Perform sanity of string parsed certificate
+ */
+ protected void certificateSanity(String certificatePem) {
+ Certificate certificate = getCertificateFromString(certificatePem);
+
+ if (certificate instanceof X509CertImpl) {
+ X509CertImpl x509Cert = (X509CertImpl) certificate;
+ try {
+ x509Cert.checkValidity();
+ } catch (CertificateExpiredException |
CertificateNotYetValidException e) {
+ String msg = "Certificate is invalid. Please provide a valid
certificate. Error: " + e.getMessage();
+ s_logger.error(msg);
+ throw new CloudRuntimeException(msg);
+ }
+ if (x509Cert.getSubjectDN() != null) {
+ s_logger.debug("Valid certificate for domain name: " +
x509Cert.getSubjectDN().getName());
+ }
+ }
+ }
+
@Override
- public boolean uploadCertificateToHosts(String certificateCer, String
certificateName, String hypervisor) {
+ public boolean uploadCertificateToHosts(String certificateCer, String
alias, String hypervisor) {
HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
List<HostVO> hosts =
getRunningHostsToUploadCertificate(hypervisorType);
+
+ String certificatePem = getPretifiedCertificate(certificateCer);
+ certificateSanity(certificatePem);
+
+ s_logger.info("Attempting to upload certificate: " + alias + " to " +
hosts.size() + " hosts");
if (CollectionUtils.isNotEmpty(hosts)) {
for (HostVO host : hosts) {
- if (!uploadCertificate(certificateCer, certificateName,
host.getId())) {
- throw new CloudRuntimeException("Uploading certificate " +
certificateName + " failed on host: " + host.getId());
+ if (!uploadCertificate(certificatePem, alias, host.getId())) {
+ String msg = "Could not upload certificate " + alias + "
on host: " + host.getName() + " (" + host.getUuid() + ")";
+ s_logger.error(msg);
+ throw new CloudRuntimeException(msg);
}
}
}
@@ -331,14 +389,18 @@ public class DirectDownloadManagerImpl extends
ManagerBase implements DirectDown
* Upload and import certificate to hostId on keystore
*/
protected boolean uploadCertificate(String certificate, String
certificateName, long hostId) {
- String cert = certificate.replaceAll("(.{64})", "$1\n");
- final String prettified_cert = BEGIN_CERT + LINE_SEPARATOR + cert +
LINE_SEPARATOR + END_CERT;
- SetupDirectDownloadCertificate cmd = new
SetupDirectDownloadCertificate(prettified_cert, certificateName);
+ SetupDirectDownloadCertificateCommand cmd = new
SetupDirectDownloadCertificateCommand(certificate, certificateName);
Answer answer = agentManager.easySend(hostId, cmd);
if (answer == null || !answer.getResult()) {
+ String msg = "Certificate " + certificateName + " could not be
added to host " + hostId;
+ if (answer != null) {
+ msg += " due to: " + answer.getDetails();
+ }
+ s_logger.error(msg);
return false;
}
s_logger.info("Certificate " + certificateName + " successfully
uploaded to host: " + hostId);
return true;
}
+
}
diff --git
a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java
b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java
index 5940599..5082500 100644
---
a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java
+++
b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java
@@ -20,6 +20,7 @@ package org.apache.cloudstack.direct.download;
import com.cloud.agent.AgentManager;
import com.cloud.host.dao.HostDao;
+import com.cloud.utils.exception.CloudRuntimeException;
import
org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol;
import org.junit.Assert;
import org.junit.Before;
@@ -50,6 +51,26 @@ public class DirectDownloadManagerImplTest {
private static final String HTTP_HEADER_2 = "Accept-Encoding";
private static final String HTTP_VALUE_2 = "gzip";
+ private static final String VALID_CERTIFICATE =
+
"MIIDSzCCAjMCFDa0LoW+1O8/cEwCI0nIqfl8c1TLMA0GCSqGSIb3DQEBCwUAMGEx\n" +
+
"CzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoM\n" +
+
"AkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNT\n" +
+
"MCAXDTE5MDQyNDE1NTIzNVoYDzIwOTgwOTE1MTU1MjM1WjBhMQswCQYDVQQGEwJD\n" +
+
"UzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UE\n" +
+
"CwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZI\n" +
+
"hvcNAQEBBQADggEPADCCAQoCggEBAKstLRcMGCo6+2hojRMjEuuimnWp27yfYhDU\n" +
+
"w/Cj03MJe/KCOhwsDqX82QNIr/bNtLdFf2ZJEUQd08sLLlHeUy9y5aOcxt9SGx2j\n" +
+
"xolqO4MBL7BW3dklO0IvjaEfBeFP6udz8ajeVur/iPPZb2Edd0zlXuHvDozfQisv\n" +
+
"bpuJImnTUVx0ReCXP075PBGvlqQXW2uEht+E/w3H8/2rra3JFV6J5xc77KyQSq2t\n" +
+
"1+2ZU7PJiy/rppXf5rjTvNm6ydfag8/av7lcgs2ntdkK4koAmkmROhAwNonlL7cD\n" +
+
"xIC83cKOqOFiQXSwr1IgoLf7zBNafKoTlSb/ev6Zt18BXEMLGpkCAwEAATANBgkq\n" +
+
"hkiG9w0BAQsFAAOCAQEAVS5uWZRz2m3yx7EUQm47RTMW5WMXU4pI8D+N5WZ9xubY\n" +
+
"OqtU3r2OAYpfL/QO8iT7jcqNYGoDqe8ZjEaNvfxiTG8cOI6TSXhKBG6hjSaSFQSH\n" +
+
"OZ5mfstM36y/3ENFh6JCJ2ao1rgWSbfDRyAaHuvt6aCkaV6zRq2OMEgoJqZSgwxL\n" +
+
"QO230xa2hYgKXOePMVZyHFA2oKJtSOc3jCke9Y8zDUwm0McGdMRBD8tVB0rcaOqQ\n" +
+
"0PlDLjB9sQuhhLu8vjdgbznmPbUmMG7JN0yhT1eJbIX5ImXyh0DoTwiaGcYwW6Sq\n" +
+ "YodjXACsC37xaQXAPYBiaAs4iI80TJSx1DVFO1LV0g==";
+
@Before
public void setUp() {
}
@@ -103,4 +124,16 @@ public class DirectDownloadManagerImplTest {
Map<String, String> headers = manager.getHeadersFromDetails(details);
Assert.assertTrue(headers.isEmpty());
}
+
+ @Test
+ public void testCertificateSanityValidCertificate() {
+ String pretifiedCertificate =
manager.getPretifiedCertificate(VALID_CERTIFICATE);
+ manager.certificateSanity(pretifiedCertificate);
+ }
+
+ @Test(expected = CloudRuntimeException.class)
+ public void testCertificateSanityInvalidCertificate() {
+ String pretifiedCertificate =
manager.getPretifiedCertificate(VALID_CERTIFICATE + "xxx");
+ manager.certificateSanity(pretifiedCertificate);
+ }
}
diff --git a/test/integration/smoke/test_direct_download.py
b/test/integration/smoke/test_direct_download.py
new file mode 100644
index 0000000..65117f9
--- /dev/null
+++ b/test/integration/smoke/test_direct_download.py
@@ -0,0 +1,227 @@
+# 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.
+""" Test for Direct Downloads of Templates and ISOs
+"""
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.utils import (cleanup_resources)
+from marvin.lib.base import (ServiceOffering,
+ NetworkOffering,
+ Network,
+ Template,
+ VirtualMachine)
+from marvin.lib.common import (get_pod,
+ get_zone)
+from nose.plugins.attrib import attr
+from marvin.cloudstackAPI import uploadTemplateDirectDownloadCertificate
+from marvin.lib.decoratorGenerators import skipTestIf
+
+
+class TestUploadDirectDownloadCertificates(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.testClient = super(TestUploadDirectDownloadCertificates,
cls).getClsTestClient()
+ cls.apiclient = cls.testClient.getApiClient()
+ cls.hypervisor = cls.testClient.getHypervisorInfo()
+ cls.dbclient = cls.testClient.getDbConnection()
+ cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+ cls.pod = get_pod(cls.apiclient, cls.zone.id)
+ cls.services = cls.testClient.getParsedTestDataConfig()
+
+ cls._cleanup = []
+ cls.hypervisorNotSupported = False
+ if cls.hypervisor.lower() not in ['kvm', 'lxc']:
+ cls.hypervisorNotSupported = True
+
+ if not cls.hypervisorNotSupported:
+ cls.certificates = {
+ "expired":
"MIIDSTCCAjECFDi8s70TWFhwVN9cj67RJoAF99c8MA0GCSqGSIb3DQEBCwUAMGExCzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoMAkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNTMB4XDTE5MDQyNDE1NTQxM1oXDTE5MDQyMjE1NTQxM1owYTELMAkGA1UEBhMCQ1MxCzAJBgNVBAgMAkNTMQswCQYDVQQHDAJDUzELMAkGA1UECgwCQ1MxCzAJBgNVBAsMAkNTMQswCQYDVQQDDAJDUzERMA8GCSqGSIb3DQEJARYCQ1MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrLS0XDBgqOvtoaI0TIxLropp1qdu8n2IQ1MPwo9NzCXvygjocLA6l
[...]
+ "invalid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "valid":
"MIIDSzCCAjMCFDa0LoW+1O8/cEwCI0nIqfl8c1TLMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoMAkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNTMCAXDTE5MDQyNDE1NTIzNVoYDzIwOTgwOTE1MTU1MjM1WjBhMQswCQYDVQQGEwJDUzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UECwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKstLRcMGCo6+2hojRMjEuuimnWp27yfYhDUw/Cj03MJe/KCOhwsDq
[...]
+ }
+
+ return
+
+ @classmethod
+ def tearDownClass(cls):
+ try:
+ cleanup_resources(cls.apiclient, cls._cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.cleanup = []
+ return
+
+ def tearDown(self):
+ try:
+ cleanup_resources(self.apiclient, self.cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ @skipTestIf("hypervisorNotSupported")
+ @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"],
required_hardware="false")
+ def test_01_sanity_check_on_certificates(self):
+ """Test Verify certificates before uploading to KVM hosts
+ """
+
+ # Validate the following
+ # 1. Invalid certificates cannot be uploaded to hosts for direct
downloads
+ # 2. Expired certificates cannot be uploaded to hosts for direct
downloads
+
+ cmd =
uploadTemplateDirectDownloadCertificate.uploadTemplateDirectDownloadCertificateCmd()
+ cmd.hypervisor = self.hypervisor
+ cmd.name = "marvin-test-verify-certs"
+ cmd.certificate = self.certificates["invalid"]
+
+ invalid_cert_uploadFails = False
+ expired_cert_upload_fails = False
+ try:
+ self.apiclient.uploadTemplateDirectDownloadCertificate(cmd)
+ self.fail("Invalid certificate must not be uploaded")
+ except Exception as e:
+ invalid_cert_uploadFails = True
+
+ cmd.certificate = self.certificates["expired"]
+ try:
+ self.apiclient.uploadTemplateDirectDownloadCertificate(cmd)
+ self.fail("Expired certificate must not be uploaded")
+ except Exception as e:
+ expired_cert_upload_fails = True
+
+ self.assertTrue(invalid_cert_uploadFails and expired_cert_upload_fails,
+ "Invalid or expired certificates must not be uploaded")
+ return
+
+ @skipTestIf("hypervisorNotSupported")
+ @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"],
required_hardware="false")
+ def test_02_upload_direct_download_certificates(self):
+ """Test Upload certificates to KVM hosts for direct download
+ """
+
+ # Validate the following
+ # 1. Valid certificates are uploaded to hosts
+
+ cmd =
uploadTemplateDirectDownloadCertificate.uploadTemplateDirectDownloadCertificateCmd()
+ cmd.hypervisor = self.hypervisor
+ cmd.name = "marvin-test-verify-certs"
+ cmd.certificate = self.certificates["valid"]
+
+ try:
+ self.apiclient.uploadTemplateDirectDownloadCertificate(cmd)
+ except Exception as e:
+ self.fail("Valid certificate must be uploaded")
+
+ return
+
+
+class TestDirectDownloadTemplates(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.testClient = super(TestDirectDownloadTemplates,
cls).getClsTestClient()
+ cls.apiclient = cls.testClient.getApiClient()
+ cls.hypervisor = cls.testClient.getHypervisorInfo()
+ cls.dbclient = cls.testClient.getDbConnection()
+ cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+ cls.pod = get_pod(cls.apiclient, cls.zone.id)
+ cls.services = cls.testClient.getParsedTestDataConfig()
+
+ cls._cleanup = []
+ cls.hypervisorNotSupported = False
+ if cls.hypervisor.lower() not in ['kvm', 'lxc']:
+ cls.hypervisorNotSupported = True
+
+ if not cls.hypervisorNotSupported:
+ cls.services["test_templates"]["kvm"]["directdownload"] = "true"
+ cls.template = Template.register(cls.apiclient,
cls.services["test_templates"]["kvm"],
+ zoneid=cls.zone.id, hypervisor=cls.hypervisor)
+ cls._cleanup.append(cls.template)
+
+ cls.services["virtual_machine"]["zoneid"] = cls.zone.id
+ cls.services["virtual_machine"]["template"] = cls.template.id
+ cls.services["virtual_machine"]["hypervisor"] = cls.hypervisor
+ cls.service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cls.services["service_offerings"]["tiny"]
+ )
+ cls._cleanup.append(cls.service_offering)
+ cls.network_offering = NetworkOffering.create(
+ cls.apiclient,
+ cls.services["l2-network_offering"],
+ )
+ cls.network_offering.update(cls.apiclient, state='Enabled')
+ cls.services["network"]["networkoffering"] =
cls.network_offering.id
+ cls.l2_network = Network.create(
+ cls.apiclient,
+ cls.services["l2-network"],
+ zoneid=cls.zone.id,
+ networkofferingid=cls.network_offering.id
+ )
+ cls._cleanup.append(cls.l2_network)
+ cls._cleanup.append(cls.network_offering)
+ return
+
+ @classmethod
+ def tearDownClass(cls):
+ try:
+ cleanup_resources(cls.apiclient, cls._cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.cleanup = []
+ return
+
+ def tearDown(self):
+ try:
+ cleanup_resources(self.apiclient, self.cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+ return
+
+ @skipTestIf("hypervisorNotSupported")
+ @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"],
required_hardware="false")
+ def test_01_deploy_vm_from_direct_download_template(self):
+ """Test Deploy VM from direct download template
+ """
+
+ # Validate the following
+ # 1. Register direct download template
+ # 2. Deploy VM from direct download template
+
+ vm = VirtualMachine.create(
+ self.apiclient,
+ self.services["virtual_machine"],
+ serviceofferingid=self.service_offering.id,
+ networkids=self.l2_network.id
+ )
+ self.assertEqual(
+ vm.state,
+ "Running",
+ "Check VM deployed from direct download template is running"
+ )
+ self.cleanup.append(vm)
+ return