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 <nicovazque...@gmail.com>
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

Reply via email to