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
This commit is contained in:
Nicolas Vazquez 2019-06-04 03:08:31 -03:00 committed by GitHub
parent c9ce3e2344
commit 12c850ed2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 472 additions and 42 deletions

View File

@ -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();

View File

@ -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,

View File

@ -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;
}

View File

@ -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");
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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/NkDSK/2zbS3RX9mSRFEHdPLCy5R3lMvcuWjnMbfUhsdo8aJajuDAS+wVt3ZJTtCL42hHwXhT+rnc/Go3lbq/4jz2W9hHXdM5V7h7w6M30IrL26biSJp01FcdEXglz9O+TwRr5akF1trhIbfhP8Nx/P9q62tyRVeiecXO+yskEqtrdftmVOzyYsv66aV3+a407zZusnX2oPP2r+5XILNp7XZCuJKAJpJkToQMDaJ5S+3A8SAvN3CjqjhYkF0sK9SIKC3+8wTWnyqE5Um/3r+mbdfAVxDCxqZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAG/R9sJ2pFbu35MliIJIhWkwP7FeP/7gYCNvOXFt6vVGXmcOwuw9WGBxsmsGESQRB4+NnJFjyGQ1Ck+ps5XRRMizyvq6bCQxVuC5M+vYS4J0q8YoL0RJ20pN9iwTsosZjSEKmfUlVgsufqCG2nyusV71LSaQU6f/bylJcJkKwGUhThExh+PVLZ66H5cF4/SzuK6WzWnj5p6+YX8TP+qPUkXN1mapgVKfVMo6mqLsH+eLKH+zqdy5ZZ5znNSbJFgHufYbEFlutTaxHEvKNMEgMCFkFGiyPwRuD6oaPnZFquJLh/mBZOLogpxVD5v20AcUTANtbXSlPaqOnEQFcbiVCb8=",
"invalid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"valid": "MIIDSzCCAjMCFDa0LoW+1O8/cEwCI0nIqfl8c1TLMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoMAkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNTMCAXDTE5MDQyNDE1NTIzNVoYDzIwOTgwOTE1MTU1MjM1WjBhMQswCQYDVQQGEwJDUzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UECwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKstLRcMGCo6+2hojRMjEuuimnWp27yfYhDUw/Cj03MJe/KCOhwsDqX82QNIr/bNtLdFf2ZJEUQd08sLLlHeUy9y5aOcxt9SGx2jxolqO4MBL7BW3dklO0IvjaEfBeFP6udz8ajeVur/iPPZb2Edd0zlXuHvDozfQisvbpuJImnTUVx0ReCXP075PBGvlqQXW2uEht+E/w3H8/2rra3JFV6J5xc77KyQSq2t1+2ZU7PJiy/rppXf5rjTvNm6ydfag8/av7lcgs2ntdkK4koAmkmROhAwNonlL7cDxIC83cKOqOFiQXSwr1IgoLf7zBNafKoTlSb/ev6Zt18BXEMLGpkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVS5uWZRz2m3yx7EUQm47RTMW5WMXU4pI8D+N5WZ9xubYOqtU3r2OAYpfL/QO8iT7jcqNYGoDqe8ZjEaNvfxiTG8cOI6TSXhKBG6hjSaSFQSHOZ5mfstM36y/3ENFh6JCJ2ao1rgWSbfDRyAaHuvt6aCkaV6zRq2OMEgoJqZSgwxLQO230xa2hYgKXOePMVZyHFA2oKJtSOc3jCke9Y8zDUwm0McGdMRBD8tVB0rcaOqQ0PlDLjB9sQuhhLu8vjdgbznmPbUmMG7JN0yhT1eJbIX5ImXyh0DoTwiaGcYwW6SqYodjXACsC37xaQXAPYBiaAs4iI80TJSx1DVFO1LV0g=="
}
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