You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by nv...@apache.org on 2022/04/12 01:57:28 UTC
[cloudstack] branch main updated: Direct download certificates additions and improvements (#6104)
This is an automated email from the ASF dual-hosted git repository.
nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 5435b0abfe4 Direct download certificates additions and improvements (#6104)
5435b0abfe4 is described below
commit 5435b0abfe47e5c6e933b651331905467cfa2574
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Mon Apr 11 22:57:23 2022 -0300
Direct download certificates additions and improvements (#6104)
* Add direct download certificates listing
* Restore class to original project
* Small refactor
* Register API
* Apply suggestions from code review
Co-authored-by: Suresh Kumar Anaparti <su...@gmail.com>
* Refactor after review
* Fix checkstyle
* Add hosts mapping to API response
* Improvements on revoke certificate
* Refactor revoke certificate API
* Fix condition
* Filter only certificates not revoked for revokeCertificate API
* Improve upload certificate and add provision certificate API
* Improve certificate response output
* Address review comments
* Refactor revoke cert test
* Fix marvin test
* Address review comments
* Fix issues
* Improvements
* Refactor upload template API response
* Fix response
Co-authored-by: Suresh Kumar Anaparti <su...@gmail.com>
---
.../org/apache/cloudstack/api/ApiConstants.java | 8 +
.../apache/cloudstack/api/ResponseGenerator.java | 13 ++
.../ListTemplateDirectDownloadCertificatesCmd.java | 110 +++++++++++
...isionTemplateDirectDownloadCertificateCmd.java} | 58 ++----
...RevokeTemplateDirectDownloadCertificateCmd.java | 72 +++++--
...UploadTemplateDirectDownloadCertificateCmd.java | 44 ++++-
...irectDownloadCertificateHostStatusResponse.java | 73 +++++++
.../DirectDownloadCertificateResponse.java | 162 +++++++++++++++
.../direct/download/DirectDownloadCertificate.java | 1 +
....java => DirectDownloadCertificateHostMap.java} | 13 +-
.../direct/download/DirectDownloadManager.java | 60 +++++-
.../DirectDownloadCertificateHostMapDao.java | 1 +
.../DirectDownloadCertificateHostMapDaoImpl.java | 9 +
.../DirectDownloadCertificateHostMapVO.java | 16 +-
framework/direct-download/pom.xml | 8 +
.../direct/download/DirectDownloadService.java | 9 +-
.../main/java/com/cloud/api/ApiResponseHelper.java | 88 +++++++++
.../com/cloud/server/ManagementServerImpl.java | 4 +
.../direct/download/DirectDownloadManagerImpl.java | 217 ++++++++++++++++-----
.../java/com/cloud/api/ApiResponseHelperTest.java | 43 ++++
test/integration/smoke/test_direct_download.py | 14 +-
21 files changed, 870 insertions(+), 153 deletions(-)
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 871f99b48f5..698d5d88213 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -26,6 +26,7 @@ public class ApiConstants {
public static final String ADAPTER_TYPE = "adaptertype";
public static final String ADDRESS = "address";
public static final String ALGORITHM = "algorithm";
+ public static final String ALIAS = "alias";
public static final String ALLOCATED_ONLY = "allocatedonly";
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
@@ -58,6 +59,10 @@ public class ApiConstants {
public static final String CERTIFICATE_CHAIN = "certchain";
public static final String CERTIFICATE_FINGERPRINT = "fingerprint";
public static final String CERTIFICATE_ID = "certid";
+ public static final String CERTIFICATE_ISSUER = "issuer";
+ public static final String CERTIFICATE_SERIALNUM = "serialnum";
+ public static final String CERTIFICATE_SUBJECT = "subject";
+ public static final String CERTIFICATE_VALIDITY = "validity";
public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck";
public static final String CONTROLLER = "controller";
public static final String CONTROLLER_UNIT = "controllerunit";
@@ -188,6 +193,7 @@ public class ApiConstants {
public static final String HOST_ID = "hostid";
public static final String HOST_IDS = "hostids";
public static final String HOST_NAME = "hostname";
+ public static final String HOSTS_MAP = "hostsmap";
public static final String HYPERVISOR = "hypervisor";
public static final String INLINE = "inline";
public static final String INSTANCE = "instance";
@@ -237,6 +243,7 @@ public class ApiConstants {
public static final String LEVEL = "level";
public static final String LENGTH = "length";
public static final String LIMIT_CPU_USE = "limitcpuuse";
+ public static final String LIST_HOSTS = "listhosts";
public static final String LOCK = "lock";
public static final String LUN = "lun";
public static final String LBID = "lbruleid";
@@ -322,6 +329,7 @@ public class ApiConstants {
public static final String RESOURCE_TYPE_NAME = "resourcetypename";
public static final String RESPONSE = "response";
public static final String REVERTABLE = "revertable";
+ public static final String REVOKED = "revoked";
public static final String REGISTERED = "registered";
public static final String QUALIFIERS = "qualifiers";
public static final String QUERY_FILTER = "queryfilter";
diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
index 03f0a3c8369..6e149b60edf 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
@@ -23,10 +23,16 @@ import java.util.Map;
import java.util.Set;
import com.cloud.server.ResourceIcon;
+import com.cloud.utils.Pair;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import com.cloud.resource.RollingMaintenanceManager;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
+import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.cloudstack.management.ManagementServerHost;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -491,4 +497,11 @@ public interface ResponseGenerator {
ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon);
+ DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate);
+
+ List<DirectDownloadCertificateHostStatusResponse> createDirectDownloadCertificateHostMapResponse(List<DirectDownloadCertificateHostMap> hostMappings);
+
+ DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus status);
+
+ DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair<Boolean, String> result);
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java
new file mode 100644
index 00000000000..21480c1ef53
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java
@@ -0,0 +1,110 @@
+// 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 org.apache.cloudstack.api.command.admin.direct.download;
+
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
+import org.apache.cloudstack.direct.download.DirectDownloadManager;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+@APICommand(name = ListTemplateDirectDownloadCertificatesCmd.APINAME,
+ description = "List the uploaded certificates for direct download templates",
+ responseObject = DirectDownloadCertificateResponse.class,
+ since = "4.17.0",
+ authorized = {RoleType.Admin})
+public class ListTemplateDirectDownloadCertificatesCmd extends BaseListCmd {
+
+ @Inject
+ DirectDownloadManager directDownloadManager;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DirectDownloadCertificateResponse.class,
+ description = "list direct download certificate by ID")
+ private Long id;
+
+ @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
+ description = "the zone where certificates are uploaded")
+ private Long zoneId;
+
+ @Parameter(name = ApiConstants.LIST_HOSTS, type = CommandType.BOOLEAN,
+ description = "if set to true: include the hosts where the certificate is uploaded to")
+ private Boolean listHosts;
+
+ private static final Logger LOG = Logger.getLogger(ListTemplateDirectDownloadCertificatesCmd.class);
+ public static final String APINAME = "listTemplateDirectDownloadCertificates";
+
+ public boolean isListHosts() {
+ return listHosts != null && listHosts;
+ }
+
+ private void createResponse(final List<DirectDownloadCertificate> certificates) {
+ final ListResponse<DirectDownloadCertificateResponse> response = new ListResponse<>();
+ final List<DirectDownloadCertificateResponse> responses = new ArrayList<>();
+ for (final DirectDownloadCertificate certificate : certificates) {
+ if (certificate == null) {
+ continue;
+ }
+ DirectDownloadCertificateResponse certificateResponse = _responseGenerator.createDirectDownloadCertificateResponse(certificate);
+ if (isListHosts()) {
+ List<DirectDownloadCertificateHostMap> hostMappings = directDownloadManager.getCertificateHostsMapping(certificate.getId());
+ List<DirectDownloadCertificateHostStatusResponse> hostMapResponses = _responseGenerator.createDirectDownloadCertificateHostMapResponse(hostMappings);
+ certificateResponse.setHostsMap(hostMapResponses);
+ }
+ responses.add(certificateResponse);
+ }
+ response.setResponses(responses);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
+ ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+ List<DirectDownloadCertificate> certificates = directDownloadManager.listDirectDownloadCertificates(id, zoneId);
+ createResponse(certificates);
+ }
+
+ @Override
+ public String getCommandName() {
+ return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccount().getId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java
similarity index 52%
copy from api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java
copy to api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java
index ef9fa8b1fa2..b7574b438d0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java
@@ -23,67 +23,47 @@ import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.HostResponse;
-import org.apache.cloudstack.api.response.SuccessResponse;
-import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
-import org.apache.log4j.Logger;
import javax.inject.Inject;
-@APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME,
- description = "Revoke a certificate alias from a KVM host",
- responseObject = SuccessResponse.class,
- requestHasSensitiveInfo = true,
- responseHasSensitiveInfo = true,
- since = "4.13",
+@APICommand(name = ProvisionTemplateDirectDownloadCertificateCmd.APINAME,
+ description = "Provisions a host with a direct download certificate",
+ responseObject = DirectDownloadCertificateHostStatusResponse.class,
+ since = "4.17.0",
authorized = {RoleType.Admin})
-public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
+public class ProvisionTemplateDirectDownloadCertificateCmd extends BaseCmd {
+
+ public static final String APINAME = "provisionTemplateDirectDownloadCertificate";
@Inject
DirectDownloadManager directDownloadManager;
- private static final Logger LOG = Logger.getLogger(RevokeTemplateDirectDownloadCertificateCmd.class);
- public static final String APINAME = "revokeTemplateDirectDownloadCertificate";
-
- @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true,
- description = "alias of the SSL certificate")
- private String certificateAlias;
-
- @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true,
- description = "hypervisor type")
- private String hypervisor;
-
- @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
- description = "zone to revoke certificate", required = true)
- private Long zoneId;
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DirectDownloadCertificateResponse.class,
+ description = "the id of the direct download certificate to provision", required = true)
+ private Long id;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
- description = "(optional) the host ID to revoke certificate")
+ description = "the host to provision the certificate", required = true)
private Long hostId;
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
- if (!hypervisor.equalsIgnoreCase("kvm")) {
- throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only");
- }
- SuccessResponse response = new SuccessResponse(getCommandName());
- try {
- LOG.debug("Revoking certificate " + certificateAlias + " from " + hypervisor + " hosts");
- boolean result = directDownloadManager.revokeCertificateAlias(certificateAlias, hypervisor, zoneId, hostId);
- response.setSuccess(result);
- setResponseObject(response);
- } catch (Exception e) {
- throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
- }
+ Pair<Boolean, String> result = directDownloadManager.provisionCertificate(id, hostId);
+ DirectDownloadCertificateHostStatusResponse response = _responseGenerator.createDirectDownloadCertificateProvisionResponse(id, hostId, result);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
}
@Override
@@ -95,4 +75,4 @@ public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
-}
\ No newline at end of file
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java
index ef9fa8b1fa2..507d73f134e 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java
@@ -30,20 +30,26 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.HostResponse;
-import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
+import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
@APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME,
- description = "Revoke a certificate alias from a KVM host",
- responseObject = SuccessResponse.class,
- requestHasSensitiveInfo = true,
- responseHasSensitiveInfo = true,
+ description = "Revoke a direct download certificate from hosts in a zone",
+ responseObject = DirectDownloadCertificateHostStatusResponse.class,
since = "4.13",
authorized = {RoleType.Admin})
public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
@@ -54,35 +60,63 @@ public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
private static final Logger LOG = Logger.getLogger(RevokeTemplateDirectDownloadCertificateCmd.class);
public static final String APINAME = "revokeTemplateDirectDownloadCertificate";
- @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true,
- description = "alias of the SSL certificate")
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID,
+ entityType = DirectDownloadCertificateResponse.class,
+ description = "id of the certificate")
+ private Long certificateId;
+
+ @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING,
+ description = "(optional) alias of the SSL certificate")
private String certificateAlias;
- @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true,
- description = "hypervisor type")
+ @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING,
+ description = "(optional) hypervisor type")
private String hypervisor;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
- description = "zone to revoke certificate", required = true)
+ description = "(optional) zone to revoke certificate", required = true)
private Long zoneId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
description = "(optional) the host ID to revoke certificate")
private Long hostId;
- @Override
- public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
- if (!hypervisor.equalsIgnoreCase("kvm")) {
+ private void createResponse(final List<HostCertificateStatus> hostsRevokeStatusList) {
+ final ListResponse<DirectDownloadCertificateHostStatusResponse> response = new ListResponse<>();
+ final List<DirectDownloadCertificateHostStatusResponse> responses = new ArrayList<>();
+ for (final HostCertificateStatus status : hostsRevokeStatusList) {
+ if (status == null) {
+ continue;
+ }
+ DirectDownloadCertificateHostStatusResponse revokeResponse =
+ _responseGenerator.createDirectDownloadCertificateHostStatusResponse(status);
+ responses.add(revokeResponse);
+ }
+ response.setResponses(responses);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ private void validateParameters() {
+ if (ObjectUtils.allNull(certificateId, certificateAlias, hypervisor) ||
+ certificateId == null && !ObjectUtils.allNotNull(certificateAlias, hypervisor)) {
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please specify the hypervisor and the" +
+ "certificate name to revoke or the certificate ID");
+ }
+ if (StringUtils.isNotBlank(hypervisor) && !hypervisor.equalsIgnoreCase("kvm")) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only");
}
- SuccessResponse response = new SuccessResponse(getCommandName());
+ }
+
+ @Override
+ public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+ validateParameters();
try {
- LOG.debug("Revoking certificate " + certificateAlias + " from " + hypervisor + " hosts");
- boolean result = directDownloadManager.revokeCertificateAlias(certificateAlias, hypervisor, zoneId, hostId);
- response.setSuccess(result);
- setResponseObject(response);
+ DirectDownloadCertificate certificate = directDownloadManager.findDirectDownloadCertificateByIdOrHypervisorAndAlias(certificateId, certificateAlias, hypervisor, zoneId);
+ List<HostCertificateStatus> hostsResult = directDownloadManager.revokeCertificate(certificate, zoneId, hostId);
+ createResponse(hostsResult);
} catch (Exception e) {
- throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed revoking certificate: " + e.getMessage());
}
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
index 223f20b5bb0..e403f11baac 100755
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
@@ -16,6 +16,8 @@
// under the License.
package org.apache.cloudstack.api.command.admin.direct.download;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
@@ -23,20 +25,23 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.HostResponse;
-import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
+import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus;
import org.apache.log4j.Logger;
import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
@APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME,
description = "Upload a certificate for HTTPS direct template download on KVM hosts",
- responseObject = SuccessResponse.class,
- requestHasSensitiveInfo = true,
- responseHasSensitiveInfo = true,
+ responseObject = DirectDownloadCertificateResponse.class,
since = "4.11.0",
authorized = {RoleType.Admin})
public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
@@ -63,9 +68,29 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
private Long zoneId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
- description = "(optional) the host ID to revoke certificate")
+ description = "(optional) the host ID to upload certificate")
private Long hostId;
+ private void createResponse(DirectDownloadCertificate certificate, final List<HostCertificateStatus> hostStatusList) {
+ final List<DirectDownloadCertificateHostStatusResponse> hostMapsResponse = new ArrayList<>();
+ if (certificate == null) {
+ throw new CloudRuntimeException("Unable to upload certificate");
+ }
+ DirectDownloadCertificateResponse response = _responseGenerator.createDirectDownloadCertificateResponse(certificate);
+ for (final HostCertificateStatus status : hostStatusList) {
+ if (status == null) {
+ continue;
+ }
+ DirectDownloadCertificateHostStatusResponse uploadResponse =
+ _responseGenerator.createDirectDownloadCertificateHostStatusResponse(status);
+ hostMapsResponse.add(uploadResponse);
+ }
+ response.setHostsMap(hostMapsResponse);
+ response.setResponseName(getCommandName());
+ response.setObjectName("uploadtemplatedirectdownloadcertificate");
+ setResponseObject(response);
+ }
+
@Override
public void execute() {
if (!hypervisor.equalsIgnoreCase("kvm")) {
@@ -74,10 +99,11 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
try {
LOG.debug("Uploading certificate " + name + " to agents for Direct Download");
- boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId);
- SuccessResponse response = new SuccessResponse(getCommandName());
- response.setSuccess(result);
- setResponseObject(response);
+ Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadStatus =
+ directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId);
+ DirectDownloadCertificate certificate = uploadStatus.first();
+ List<HostCertificateStatus> hostStatus = uploadStatus.second();
+ createResponse(certificate, hostStatus);
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DirectDownloadCertificateHostStatusResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DirectDownloadCertificateHostStatusResponse.java
new file mode 100644
index 00000000000..cc9f2fc366a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/DirectDownloadCertificateHostStatusResponse.java
@@ -0,0 +1,73 @@
+// 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 org.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+public class DirectDownloadCertificateHostStatusResponse extends BaseResponse {
+
+ @SerializedName(ApiConstants.HOST_ID)
+ @Param(description = "the ID of the host")
+ private String hostId;
+
+ @SerializedName(ApiConstants.HOST_NAME)
+ @Param(description = "the name of the host")
+ private String hostName;
+
+ @SerializedName(ApiConstants.STATUS)
+ @Param(description = "indicates if the certificate has been revoked from the host, failed or skipped")
+ private String status;
+
+ @SerializedName(ApiConstants.DETAILS)
+ @Param(description = "indicates the details in case of failure or host skipped")
+ private String details;
+
+ public String getHostId() {
+ return hostId;
+ }
+
+ public void setHostId(String hostId) {
+ this.hostId = hostId;
+ }
+
+ public String getHostName() {
+ return hostName;
+ }
+
+ public void setHostName(String hostName) {
+ this.hostName = hostName;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ public void setDetails(String details) {
+ this.details = details;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DirectDownloadCertificateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DirectDownloadCertificateResponse.java
new file mode 100644
index 00000000000..f04cba812ba
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/DirectDownloadCertificateResponse.java
@@ -0,0 +1,162 @@
+// 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 org.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
+
+import java.util.List;
+
+@EntityReference(value = DirectDownloadCertificate.class)
+public class DirectDownloadCertificateResponse extends BaseResponse {
+
+ @SerializedName(ApiConstants.ID)
+ @Param(description = "the direct download certificate id")
+ private String id;
+
+ @SerializedName(ApiConstants.ALIAS)
+ @Param(description = "the direct download certificate alias")
+ private String alias;
+
+ @SerializedName(ApiConstants.ZONE_ID)
+ @Param(description = "the zone id where the certificate is uploaded")
+ private String zoneId;
+
+ @SerializedName(ApiConstants.ZONE_NAME)
+ @Param(description = "the zone name where the certificate is uploaded")
+ private String zoneName;
+
+ @SerializedName(ApiConstants.VERSION)
+ @Param(description = "the direct download certificate version")
+ private String version;
+
+ @SerializedName(ApiConstants.CERTIFICATE_SUBJECT)
+ @Param(description = "the direct download certificate subject")
+ private String subject;
+
+ @SerializedName(ApiConstants.CERTIFICATE_ISSUER)
+ @Param(description = "the direct download certificate issuer")
+ private String issuer;
+
+ @SerializedName(ApiConstants.CERTIFICATE_VALIDITY)
+ @Param(description = "the direct download certificate issuer")
+ private String validity;
+
+ @SerializedName(ApiConstants.CERTIFICATE_SERIALNUM)
+ @Param(description = "the direct download certificate serial num")
+ private String serialNum;
+
+ @SerializedName(ApiConstants.HYPERVISOR)
+ @Param(description = "the hypervisor of the hosts where the certificate is uploaded")
+ private String hypervisor;
+
+ @SerializedName(ApiConstants.HOSTS_MAP)
+ @Param(description = "the hosts where the certificate is uploaded to", responseObject = HostResponse.class)
+ private List<DirectDownloadCertificateHostStatusResponse> hostsMap;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public String getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(String zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public String getHypervisor() {
+ return hypervisor;
+ }
+
+ public void setHypervisor(String hypervisor) {
+ this.hypervisor = hypervisor;
+ }
+
+ public String getZoneName() {
+ return zoneName;
+ }
+
+ public void setZoneName(String zoneName) {
+ this.zoneName = zoneName;
+ }
+
+ public List<DirectDownloadCertificateHostStatusResponse> getHostsMap() {
+ return hostsMap;
+ }
+
+ public void setHostsMap(List<DirectDownloadCertificateHostStatusResponse> hosts) {
+ this.hostsMap = hosts;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public String getIssuer() {
+ return issuer;
+ }
+
+ public void setIssuer(String issuer) {
+ this.issuer = issuer;
+ }
+
+ public String getValidity() {
+ return validity;
+ }
+
+ public void setValidity(String validity) {
+ this.validity = validity;
+ }
+
+ public String getSerialNum() {
+ return serialNum;
+ }
+
+ public void setSerialNum(String serialNum) {
+ this.serialNum = serialNum;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
index 6227c26ceab..9c70d84f76d 100644
--- a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
+++ b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
@@ -25,5 +25,6 @@ public interface DirectDownloadCertificate extends InternalIdentity, Identity {
String getCertificate();
String getAlias();
Hypervisor.HypervisorType getHypervisorType();
+ Long getZoneId();
}
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMap.java
similarity index 77%
copy from api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
copy to api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMap.java
index 6227c26ceab..d48eb5564c9 100644
--- a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
+++ b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMap.java
@@ -16,14 +16,11 @@
// under the License.
package org.apache.cloudstack.direct.download;
-import com.cloud.hypervisor.Hypervisor;
-import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
-public interface DirectDownloadCertificate extends InternalIdentity, Identity {
+public interface DirectDownloadCertificateHostMap extends InternalIdentity {
- String getCertificate();
- String getAlias();
- Hypervisor.HypervisorType getHypervisorType();
-
-}
\ No newline at end of file
+ long getCertificateId();
+ long getHostId();
+ boolean isRevoked();
+}
diff --git a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
index f7dfae132a8..8a74965ceca 100644
--- a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
+++ b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
@@ -17,17 +17,21 @@
package org.apache.cloudstack.direct.download;
+import com.cloud.host.Host;
+import com.cloud.utils.Pair;
import org.apache.cloudstack.framework.agent.direct.download.DirectDownloadService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import com.cloud.utils.component.PluggableService;
+import java.util.List;
+
public interface DirectDownloadManager extends DirectDownloadService, PluggableService, Configurable {
- static final int DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT = 5000;
- static final int DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT = 5000;
- static final int DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT = 5000;
+ int DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT = 5000;
+ int DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT = 5000;
+ int DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT = 5000;
ConfigKey<Long> DirectDownloadCertificateUploadInterval = new ConfigKey<>("Advanced", Long.class,
"direct.download.certificate.background.task.interval",
@@ -37,26 +41,66 @@ public interface DirectDownloadManager extends DirectDownloadService, PluggableS
"Only certificates which have not been revoked from hosts are uploaded",
false);
- static final ConfigKey<Integer> DirectDownloadConnectTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
+ ConfigKey<Integer> DirectDownloadConnectTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
"direct.download.connect.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT),
"Connection establishment timeout in milliseconds for direct download",
true);
- static final ConfigKey<Integer> DirectDownloadSocketTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
+ ConfigKey<Integer> DirectDownloadSocketTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
"direct.download.socket.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT),
"Socket timeout (SO_TIMEOUT) in milliseconds for direct download",
true);
- static final ConfigKey<Integer> DirectDownloadConnectionRequestTimeout = new ConfigKey<Integer>("Hidden", Integer.class,
+ ConfigKey<Integer> DirectDownloadConnectionRequestTimeout = new ConfigKey<Integer>("Hidden", Integer.class,
"direct.download.connection.request.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT),
"Requesting a connection from connection manager timeout in milliseconds for direct download",
true);
+ class HostCertificateStatus {
+ public enum CertificateStatus {
+ REVOKED, FAILED, SKIPPED, UPLOADED
+ }
+
+ HostCertificateStatus(CertificateStatus status, Host host, String details) {
+ this.status = status;
+ this.host = host;
+ this.details = details;
+ }
+
+ private CertificateStatus status;
+ private Host host;
+ private String details;
+
+ public CertificateStatus getStatus() {
+ return status;
+ }
+
+ public Host getHost() {
+ return host;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+ }
+
+ DirectDownloadCertificate findDirectDownloadCertificateByIdOrHypervisorAndAlias(Long id, String alias, String hypervisor, Long zoneId);
+
+ /**
+ * Revoke direct download certificate from the hosts in the zone or a specific host
+ */
+ List<HostCertificateStatus> revokeCertificate(DirectDownloadCertificate certificate, Long zoneId, Long hostId);
+
+ List<DirectDownloadCertificate> listDirectDownloadCertificates(Long certificateId, Long zoneId);
+
+ List<DirectDownloadCertificateHostMap> getCertificateHostsMapping(Long certificateId);
+
/**
- * Revoke direct download certificate with alias 'alias' from hosts of hypervisor type 'hypervisor'
+ * Upload client certificate to each running host
+ * @return
*/
- boolean revokeCertificateAlias(String certificateAlias, String hypervisor, Long zoneId, Long hostId);
+ Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor, Long zoneId, Long hostId);
}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java
index e119b1d491e..fa056f374c9 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java
@@ -23,4 +23,5 @@ import java.util.List;
public interface DirectDownloadCertificateHostMapDao extends GenericDao<DirectDownloadCertificateHostMapVO, Long> {
DirectDownloadCertificateHostMapVO findByCertificateAndHost(long certificateId, long hostId);
List<DirectDownloadCertificateHostMapVO> listByCertificateId(long certificateId);
+ List<DirectDownloadCertificateHostMapVO> listByCertificateIdAndRevoked(long certificateId, boolean revoked);
}
\ No newline at end of file
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDaoImpl.java
index 7a0b732bbfd..de895cb6f98 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDaoImpl.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDaoImpl.java
@@ -29,6 +29,7 @@ public class DirectDownloadCertificateHostMapDaoImpl extends GenericDaoBase<Dire
mapSearchBuilder = createSearchBuilder();
mapSearchBuilder.and("certificate_id", mapSearchBuilder.entity().getCertificateId(), SearchCriteria.Op.EQ);
mapSearchBuilder.and("host_id", mapSearchBuilder.entity().getHostId(), SearchCriteria.Op.EQ);
+ mapSearchBuilder.and("revoked", mapSearchBuilder.entity().isRevoked(), SearchCriteria.Op.EQ);
mapSearchBuilder.done();
}
@Override
@@ -45,4 +46,12 @@ public class DirectDownloadCertificateHostMapDaoImpl extends GenericDaoBase<Dire
sc.setParameters("certificate_id", certificateId);
return listBy(sc);
}
+
+ @Override
+ public List<DirectDownloadCertificateHostMapVO> listByCertificateIdAndRevoked(long certificateId, boolean revoked) {
+ SearchCriteria<DirectDownloadCertificateHostMapVO> sc = mapSearchBuilder.create();
+ sc.setParameters("certificate_id", certificateId);
+ sc.setParameters("revoked", revoked);
+ return listBy(sc);
+ }
}
\ No newline at end of file
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java
index db5faf669ff..7bcfeb65860 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java
@@ -25,12 +25,12 @@ import javax.persistence.Table;
@Entity
@Table(name = "direct_download_certificate_host_map")
-public class DirectDownloadCertificateHostMapVO {
+public class DirectDownloadCertificateHostMapVO implements DirectDownloadCertificateHostMap {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
- private Long id;
+ private long id;
@Column(name = "host_id")
private Long hostId;
@@ -50,15 +50,15 @@ public class DirectDownloadCertificateHostMapVO {
this.revoked = false;
}
- public Long getId() {
+ public long getId() {
return id;
}
- public void setId(Long id) {
+ public void setId(long id) {
this.id = id;
}
- public Long getHostId() {
+ public long getHostId() {
return hostId;
}
@@ -66,7 +66,7 @@ public class DirectDownloadCertificateHostMapVO {
this.hostId = hostId;
}
- public Long getCertificateId() {
+ public long getCertificateId() {
return certificateId;
}
@@ -74,8 +74,8 @@ public class DirectDownloadCertificateHostMapVO {
this.certificateId = certificateId;
}
- public Boolean isRevoked() {
- return revoked;
+ public boolean isRevoked() {
+ return revoked != null && revoked;
}
public void setRevoked(Boolean revoked) {
diff --git a/framework/direct-download/pom.xml b/framework/direct-download/pom.xml
index 03a37532e5b..e2ce16e27e4 100644
--- a/framework/direct-download/pom.xml
+++ b/framework/direct-download/pom.xml
@@ -21,6 +21,14 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-framework-direct-download</artifactId>
<name>Apache CloudStack Framework - Direct Download to Primary Storage</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.cloudstack</groupId>
+ <artifactId>cloud-utils</artifactId>
+ <version>4.17.0.0-SNAPSHOT</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
<parent>
<artifactId>cloudstack-framework</artifactId>
<groupId>org.apache.cloudstack</groupId>
diff --git a/framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java b/framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
index 983f935a2fa..6aeb16b5d85 100644
--- a/framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
+++ b/framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
@@ -17,6 +17,8 @@
package org.apache.cloudstack.framework.agent.direct.download;
+import com.cloud.utils.Pair;
+
public interface DirectDownloadService {
/**
@@ -24,15 +26,10 @@ public interface DirectDownloadService {
*/
void downloadTemplate(long templateId, long poolId, long hostId);
- /**
- * Upload client certificate to each running host
- */
- boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor, Long zoneId, Long hostId);
-
/**
* Upload a stored certificate on database with id 'certificateId' to host with id 'hostId'
*/
- boolean uploadCertificate(long certificateId, long hostId);
+ Pair<Boolean, String> provisionCertificate(long certificateId, long hostId);
/**
* Sync the stored certificates to host with id 'hostId'
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index e0cc8509d08..e8ac1628c64 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -18,6 +18,8 @@ package com.cloud.api;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -34,6 +36,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
+import com.cloud.utils.security.CertificateHelper;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup;
@@ -67,6 +70,7 @@ import org.apache.cloudstack.api.response.ControlledViewEntityResponse;
import org.apache.cloudstack.api.response.CounterResponse;
import org.apache.cloudstack.api.response.CreateCmdResponse;
import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
@@ -118,6 +122,7 @@ import org.apache.cloudstack.api.response.ResourceCountResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.ResourceLimitResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
@@ -160,11 +165,15 @@ import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
+import org.apache.cloudstack.direct.download.DirectDownloadManager;
+import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus.CertificateStatus;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.management.ManagementServerHost;
@@ -363,6 +372,7 @@ import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.commons.lang3.StringUtils;
+import sun.security.x509.X509CertImpl;
public class ApiResponseHelper implements ResponseGenerator {
@@ -4547,4 +4557,82 @@ public class ApiResponseHelper implements ResponseGenerator {
public ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon) {
return ApiDBUtils.newResourceIconResponse(resourceIcon);
}
+
+ protected void handleCertificateResponse(String certStr, DirectDownloadCertificateResponse response) {
+ try {
+ Certificate cert = CertificateHelper.buildCertificate(certStr);
+ if (cert instanceof X509CertImpl) {
+ X509CertImpl certificate = (X509CertImpl) cert;
+ response.setVersion(String.valueOf(certificate.getVersion()));
+ response.setSubject(certificate.getSubjectDN().toString());
+ response.setIssuer(certificate.getIssuerDN().toString());
+ response.setSerialNum(certificate.getSerialNumberObject().toString());
+ response.setValidity(String.format("From: [%s] - To: [%s]", certificate.getNotBefore(), certificate.getNotAfter()));
+ }
+ } catch (CertificateException e) {
+ s_logger.error("Error parsing direct download certificate: " + certStr, e);
+ }
+ }
+
+ @Override
+ public DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate) {
+ DirectDownloadCertificateResponse response = new DirectDownloadCertificateResponse();
+ DataCenterVO datacenter = ApiDBUtils.findZoneById(certificate.getZoneId());
+ if (datacenter != null) {
+ response.setZoneId(datacenter.getUuid());
+ response.setZoneName(datacenter.getName());
+ }
+ response.setId(certificate.getUuid());
+ response.setAlias(certificate.getAlias());
+ handleCertificateResponse(certificate.getCertificate(), response);
+ response.setHypervisor(certificate.getHypervisorType().name());
+ response.setObjectName("directdownloadcertificate");
+ return response;
+ }
+
+ @Override
+ public List<DirectDownloadCertificateHostStatusResponse> createDirectDownloadCertificateHostMapResponse(List<DirectDownloadCertificateHostMap> hostMappings) {
+ if (CollectionUtils.isEmpty(hostMappings)) {
+ return new ArrayList<>();
+ }
+ List<DirectDownloadCertificateHostStatusResponse> responses = new ArrayList<>(hostMappings.size());
+ for (DirectDownloadCertificateHostMap map : hostMappings) {
+ DirectDownloadCertificateHostStatusResponse response = new DirectDownloadCertificateHostStatusResponse();
+ HostVO host = ApiDBUtils.findHostById(map.getHostId());
+ if (host != null) {
+ response.setHostId(host.getUuid());
+ response.setHostName(host.getName());
+ }
+ response.setStatus(map.isRevoked() ? CertificateStatus.REVOKED.name() : CertificateStatus.UPLOADED.name());
+ response.setObjectName("directdownloadcertificatehoststatus");
+ responses.add(response);
+ }
+ return responses;
+ }
+
+ private DirectDownloadCertificateHostStatusResponse getDirectDownloadHostStatusResponseInternal(Host host, CertificateStatus status, String details) {
+ DirectDownloadCertificateHostStatusResponse response = new DirectDownloadCertificateHostStatusResponse();
+ if (host != null) {
+ response.setHostId(host.getUuid());
+ response.setHostName(host.getName());
+ }
+ response.setStatus(status.name());
+ response.setDetails(details);
+ response.setObjectName("directdownloadcertificatehoststatus");
+ return response;
+ }
+
+ @Override
+ public DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus hostStatus) {
+ Host host = hostStatus.getHost();
+ CertificateStatus status = hostStatus.getStatus();
+ return getDirectDownloadHostStatusResponseInternal(host, status, hostStatus.getDetails());
+ }
+
+ @Override
+ public DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair<Boolean, String> result) {
+ HostVO host = ApiDBUtils.findHostById(hostId);
+ CertificateStatus status = result != null && result.first() ? CertificateStatus.UPLOADED : CertificateStatus.FAILED;
+ return getDirectDownloadHostStatusResponseInternal(host, status, result != null ? result.second() : "provision certificate failure");
+ }
}
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 4477eb85d8c..eae1dda4209 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -74,6 +74,8 @@ import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilities
import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
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.ListTemplateDirectDownloadCertificatesCmd;
+import org.apache.cloudstack.api.command.admin.direct.download.ProvisionTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.direct.download.RevokeTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.domain.CreateDomainCmd;
@@ -3547,6 +3549,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(DeleteManagementNetworkIpRangeCmd.class);
cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class);
cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class);
+ cmdList.add(ListTemplateDirectDownloadCertificatesCmd.class);
+ cmdList.add(ProvisionTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListMgmtsCmd.class);
cmdList.add(GetUploadParamsForIsoCmd.class);
cmdList.add(GetRouterHealthCheckResultsCmd.class);
diff --git a/server/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
index 8efe8654026..242dc865b93 100644
--- a/server/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java
@@ -19,6 +19,7 @@
package org.apache.cloudstack.direct.download;
import static com.cloud.storage.Storage.ImageFormat;
+import static org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus.CertificateStatus;
import java.net.URI;
import java.net.URISyntaxException;
@@ -29,6 +30,7 @@ import java.security.cert.CertificateNotYetValidException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
@@ -39,6 +41,8 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.Pair;
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol;
@@ -66,6 +70,7 @@ import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@@ -448,7 +453,8 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
}
@Override
- public boolean uploadCertificateToHosts(String certificateCer, String alias, String hypervisor, Long zoneId, Long hostId) {
+ public Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadCertificateToHosts(
+ String certificateCer, String alias, String hypervisor, Long zoneId, Long hostId) {
if (alias != null && (alias.equalsIgnoreCase("cloud") || alias.startsWith("cloudca"))) {
throw new CloudRuntimeException("Please provide a different alias name for the certificate");
}
@@ -457,6 +463,10 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateVO certificateVO;
HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
+ if (hypervisorType != HypervisorType.KVM) {
+ throw new CloudRuntimeException("Direct download certificates only supported on KVM");
+ }
+
if (hostId == null) {
hosts = getRunningHostsToUploadCertificate(zoneId, hypervisorType);
@@ -475,48 +485,55 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
certificateVO = directDownloadCertificateDao.findByAlias(alias, hypervisorType, zoneId);
if (certificateVO == null) {
s_logger.info("Certificate must be uploaded on zone " + zoneId);
- return false;
+ return new Pair<>(certificateVO, new ArrayList<>());
}
}
s_logger.info("Attempting to upload certificate: " + alias + " to " + hosts.size() + " hosts on zone " + zoneId);
- int hostCount = 0;
+ int success = 0;
+ int failed = 0;
+ List<HostCertificateStatus> results = new ArrayList<>();
if (CollectionUtils.isNotEmpty(hosts)) {
for (HostVO host : hosts) {
- if (!uploadCertificate(certificateVO.getId(), host.getId())) {
- String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + ")";
+ if (host == null) {
+ continue;
+ }
+ HostCertificateStatus hostStatus;
+ Pair<Boolean, String> result = provisionCertificate(certificateVO.getId(), host.getId());
+ if (!result.first()) {
+ String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + "): " + result.second();
s_logger.error(msg);
- throw new CloudRuntimeException(msg);
+ failed++;
+ hostStatus = new HostCertificateStatus(CertificateStatus.FAILED, host, result.second());
+ } else {
+ success++;
+ hostStatus = new HostCertificateStatus(CertificateStatus.UPLOADED, host, "");
}
- hostCount++;
+ results.add(hostStatus);
}
}
- s_logger.info("Certificate was successfully uploaded to " + hostCount + " hosts");
- return true;
+ s_logger.info("Certificate was successfully uploaded to " + success + " hosts, " + failed + " failed");
+ return new Pair<>(certificateVO, results);
}
- /**
- * Upload and import certificate to hostId on keystore
- */
- public boolean uploadCertificate(long certificateId, long hostId) {
- DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findById(certificateId);
- if (certificateVO == null) {
- throw new CloudRuntimeException("Could not find certificate with id " + certificateId + " to upload to host: " + hostId);
- }
-
- String certificate = certificateVO.getCertificate();
- String alias = certificateVO.getAlias();
+ private Pair<Boolean, String> setupCertificateOnHost(DirectDownloadCertificate certificate, long hostId) {
+ String certificateStr = certificate.getCertificate();
+ String alias = certificate.getAlias();
+ long certificateId = certificate.getId();
- s_logger.debug("Uploading certificate: " + certificateVO.getAlias() + " to host " + hostId);
- SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificate, alias);
+ s_logger.debug("Uploading certificate: " + alias + " to host " + hostId);
+ SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificateStr, alias);
Answer answer = agentManager.easySend(hostId, cmd);
+ Pair<Boolean, String> result;
if (answer == null || !answer.getResult()) {
String msg = "Certificate " + alias + " could not be added to host " + hostId;
if (answer != null) {
msg += " due to: " + answer.getDetails();
}
s_logger.error(msg);
- return false;
+ result = new Pair<>(false, msg);
+ } else {
+ result = new Pair<>(true, "OK");
}
s_logger.info("Certificate " + alias + " successfully uploaded to host: " + hostId);
@@ -528,8 +545,25 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateHostMapVO mapVO = new DirectDownloadCertificateHostMapVO(certificateId, hostId);
directDownloadCertificateHostMapDao.persist(mapVO);
}
+ return result;
+ }
+ /**
+ * Upload and import certificate to hostId on keystore
+ */
+ public Pair<Boolean, String> provisionCertificate(long certificateId, long hostId) {
+ DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findById(certificateId);
+ if (certificateVO == null) {
+ throw new CloudRuntimeException("Could not find certificate with id " + certificateId + " to upload to host: " + hostId);
+ }
+ HostVO host = hostDao.findById(hostId);
+ if (host == null) {
+ throw new CloudRuntimeException("Cannot find a host with ID " + hostId);
+ }
+ if (host.getHypervisorType() != HypervisorType.KVM) {
+ throw new CloudRuntimeException("Cannot provision certificate to host " + host.getName() + " since it is not KVM");
+ }
- return true;
+ return setupCertificateOnHost(certificateVO, hostId);
}
@Override
@@ -549,8 +583,9 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateHostMapVO mapping = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId);
if (mapping == null) {
s_logger.debug("Syncing certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", uploading it");
- if (!uploadCertificate(certificateVO.getId(), hostId)) {
- String msg = "Could not sync certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", upload failed";
+ Pair<Boolean, String> result = provisionCertificate(certificateVO.getId(), hostId);
+ if (!result.first()) {
+ String msg = "Could not sync certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", upload failed: " + result.second();
s_logger.error(msg);
syncCertificatesResult = false;
} else {
@@ -565,52 +600,127 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
return syncCertificatesResult;
}
- @Override
- public boolean revokeCertificateAlias(String certificateAlias, String hypervisor, Long zoneId, Long hostId) {
- HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
- DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findByAlias(certificateAlias, hypervisorType, zoneId);
- if (certificateVO == null) {
- throw new CloudRuntimeException("Certificate alias " + certificateAlias + " does not exist");
- }
-
- List<DirectDownloadCertificateHostMapVO> maps = null;
+ private List<DirectDownloadCertificateHostMapVO> getCertificateHostMappings(DirectDownloadCertificate certificate, Long hostId) {
+ List<DirectDownloadCertificateHostMapVO> maps;
if (hostId == null) {
- maps = directDownloadCertificateHostMapDao.listByCertificateId(certificateVO.getId());
+ maps = directDownloadCertificateHostMapDao.listByCertificateIdAndRevoked(certificate.getId(), false);
} else {
- DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId);
+ DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificate.getId(), hostId);
if (hostMap == null) {
- s_logger.info("Certificate " + certificateAlias + " cannot be revoked from host " + hostId + " as it is not available on the host");
- return false;
+ String msg = "Certificate " + certificate.getAlias() + " cannot be revoked from host " + hostId + " as it is not available on the host";
+ s_logger.error(msg);
+ throw new CloudRuntimeException(msg);
+ } else if (hostMap.isRevoked()) {
+ s_logger.debug("Certificate " + certificate.getAlias() + " was already revoked from host " + hostId + " skipping it");
+ return new LinkedList<>();
}
maps = Collections.singletonList(hostMap);
}
+ return maps;
+ }
+
+ @Override
+ public DirectDownloadCertificate findDirectDownloadCertificateByIdOrHypervisorAndAlias(Long id, String alias, String hypervisor, Long zoneId) {
+ DirectDownloadCertificateVO certificateVO;
+ if (id != null) {
+ certificateVO = directDownloadCertificateDao.findById(id);
+ } else if (StringUtils.isNotBlank(alias) && StringUtils.isNotBlank(hypervisor)) {
+ certificateVO = directDownloadCertificateDao.findByAlias(alias, HypervisorType.getType(hypervisor), zoneId);
+ } else {
+ throw new CloudRuntimeException("Please provide a hypervisor and certificate alias or certificate ID");
+ }
+ if (certificateVO == null) {
+ throw new CloudRuntimeException("Could not find certificate " +
+ (id != null ? "with ID " + id : "with alias " + alias + " and hypervisor " + hypervisor));
+ }
+ return certificateVO;
+ }
+
+ @Override
+ public List<HostCertificateStatus> revokeCertificate(DirectDownloadCertificate certificate, Long zoneId, Long hostId) {
+ String certificateAlias = certificate.getAlias();
+ if (!certificate.getZoneId().equals(zoneId)) {
+ throw new CloudRuntimeException("The certificate with alias " + certificateAlias + " was uploaded " +
+ " to the zone with ID=" + certificate.getZoneId() + " instead of the zone with ID=" + zoneId);
+ }
+ List<HostCertificateStatus> hostsList = new ArrayList<>();
+ List<DirectDownloadCertificateHostMapVO> maps = getCertificateHostMappings(certificate, hostId);
+ if (CollectionUtils.isEmpty(maps)) {
+ return hostsList;
+ }
+
+ int success = 0;
+ int failed = 0;
+ int skipped = 0;
s_logger.info("Attempting to revoke certificate alias: " + certificateAlias + " from " + maps.size() + " hosts");
- if (CollectionUtils.isNotEmpty(maps)) {
- for (DirectDownloadCertificateHostMapVO map : maps) {
- Long mappingHostId = map.getHostId();
- if (!revokeCertificateAliasFromHost(certificateAlias, mappingHostId)) {
- String msg = "Could not revoke certificate from host: " + mappingHostId;
- s_logger.error(msg);
- throw new CloudRuntimeException(msg);
+ for (DirectDownloadCertificateHostMapVO map : maps) {
+ Long mappingHostId = map.getHostId();
+ HostVO host = hostDao.findById(mappingHostId);
+ HostCertificateStatus hostStatus;
+ if (host == null || host.getDataCenterId() != zoneId || host.getHypervisorType() != HypervisorType.KVM) {
+ if (host != null) {
+ String reason = host.getDataCenterId() != zoneId ? "Host is not in the zone " + zoneId : "Host hypervisor is not KVM";
+ s_logger.debug("Skipping host " + host.getName() + ": " + reason);
+ hostStatus = new HostCertificateStatus(CertificateStatus.SKIPPED, host, reason);
+ hostsList.add(hostStatus);
}
+ skipped++;
+ continue;
+ }
+ Pair<Boolean, String> result = revokeCertificateAliasFromHost(certificateAlias, mappingHostId);
+ if (!result.first()) {
+ String msg = "Could not revoke certificate from host: " + mappingHostId + ": " + result.second();
+ s_logger.error(msg);
+ hostStatus = new HostCertificateStatus(CertificateStatus.FAILED, host, result.second());
+ failed++;
+ } else {
s_logger.info("Certificate " + certificateAlias + " revoked from host " + mappingHostId);
map.setRevoked(true);
+ hostStatus = new HostCertificateStatus(CertificateStatus.REVOKED, host, null);
+ success++;
directDownloadCertificateHostMapDao.update(map.getId(), map);
}
+ hostsList.add(hostStatus);
}
- return true;
+ s_logger.info(String.format("Certificate alias %s revoked from: %d hosts, %d failed, %d skipped",
+ certificateAlias, success, failed, skipped));
+ return hostsList;
+ }
+
+ @Override
+ public List<DirectDownloadCertificate> listDirectDownloadCertificates(Long certificateId, Long zoneId) {
+ if (zoneId != null && dataCenterDao.findById(zoneId) == null) {
+ throw new InvalidParameterValueException("Cannot find a zone with ID = " + zoneId);
+ }
+ List<DirectDownloadCertificate> certificates = new LinkedList<>();
+ if (certificateId != null) {
+ certificates.add(directDownloadCertificateDao.findById(certificateId));
+ } else if (zoneId != null) {
+ certificates.addAll(directDownloadCertificateDao.listByZone(zoneId));
+ } else {
+ certificates.addAll(directDownloadCertificateDao.listAll());
+ }
+ return certificates;
+ }
+
+ @Override
+ public List<DirectDownloadCertificateHostMap> getCertificateHostsMapping(Long certificateId) {
+ if (certificateId == null) {
+ throw new InvalidParameterValueException("Please specify a certificate ID");
+ }
+ return new LinkedList<>(directDownloadCertificateHostMapDao.listByCertificateId(certificateId));
}
- protected boolean revokeCertificateAliasFromHost(String alias, Long hostId) {
+ protected Pair<Boolean, String> revokeCertificateAliasFromHost(String alias, Long hostId) {
RevokeDirectDownloadCertificateCommand cmd = new RevokeDirectDownloadCertificateCommand(alias);
try {
Answer answer = agentManager.send(hostId, cmd);
- return answer != null && answer.getResult();
+ return new Pair<>(answer != null && answer.getResult(), answer != null ? answer.getDetails() : "");
} catch (AgentUnavailableException | OperationTimedoutException e) {
s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e);
+ return new Pair<>(false, e.getMessage());
}
- return false;
}
@Override
@@ -692,10 +802,13 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
s_logger.debug("Certificate " + certificateVO.getId() +
" (" + certificateVO.getAlias() + ") was not uploaded to host: " + hostVO.getId() +
" uploading it");
- boolean result = directDownloadManager.uploadCertificate(certificateVO.getId(), hostVO.getId());
+ Pair<Boolean, String> result = directDownloadManager.provisionCertificate(certificateVO.getId(), hostVO.getId());
s_logger.debug("Certificate " + certificateVO.getAlias() + " " +
- (result ? "uploaded" : "could not be uploaded") +
+ (result.first() ? "uploaded" : "could not be uploaded") +
" to host " + hostVO.getId());
+ if (!result.first()) {
+ s_logger.error("Certificate " + certificateVO.getAlias() + " failed: " + result.second());
+ }
}
}
}
diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
index 69ac86de876..6dc96cfee1f 100644
--- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
+++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
@@ -27,6 +27,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.usage.UsageService;
@@ -141,4 +142,46 @@ public class ApiResponseHelperTest {
assertTrue(response.getIpAddr().equals("ipv6"));
}
+ @Test
+ public void testHandleCertificateResponse() {
+ String certStr = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIGLTCCBRWgAwIBAgIQOHZRhOAYLowYNcopBvxCdjANBgkqhkiG9w0BAQsFADCB\n" +
+ "jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" +
+ "A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD\n" +
+ "Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB\n" +
+ "MB4XDTIxMDYxNTAwMDAwMFoXDTIyMDcxNjIzNTk1OVowFzEVMBMGA1UEAwwMKi5h\n" +
+ "cGFjaGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4UoHCmK5\n" +
+ "XdbyZ++d2BGuX35zZcESvr4K1Hw7ZTbyzMC+uokBKJcng1Hf5ctjUFKCoz7AlWRq\n" +
+ "JH5U3vU0y515C0aEE+j0lUHlxMGQD2ut+sJ6BZqcTBl5d8ns1TSckEH31DBDN3Fw\n" +
+ "uMLqEWBOjwt1MMT3Z+kR7ekuheJYbYHbJ2VtnKQd4jHmLly+/p+UqaQ6dIvQxq82\n" +
+ "ggZIUNWjGKwXS2vKl6O9EDu/QaAX9e059pf3UxAxGtJjeKXWJvt1e96T53+2+kXp\n" +
+ "j0/PuyT6F0o+grY08tCJnw7kTB4sE2qfALdwSblvyjBDOYtS4Xj5nycMpd+4Qse4\n" +
+ "2+irNBdZ63pqqQIDAQABo4IC+jCCAvYwHwYDVR0jBBgwFoAUjYxexFStiuF36Zv5\n" +
+ "mwXhuAGNYeEwHQYDVR0OBBYEFH+9CNXAwWW4+jyizee51r8x4ofHMA4GA1UdDwEB\n" +
+ "/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\n" +
+ "BQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0\n" +
+ "dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2\n" +
+ "ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29SU0FE\n" +
+ "b21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdo\n" +
+ "dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAjBgNVHREEHDAaggwqLmFwYWNoZS5vcmeC\n" +
+ "CmFwYWNoZS5vcmcwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2AEalVet1+pEg\n" +
+ "MLWiiWn0830RLEF0vv1JuIWr8vxw/m1HAAABehHLqfgAAAQDAEcwRQIgINH3CquJ\n" +
+ "zTAprwjdo2cEWkMzpaNoP1SOI4xGl68PF2oCIQC77eD7K6Smx4Fv/z/sTKk21Psb\n" +
+ "ZhmVq5YoqhwRKuMgVAB2AEHIyrHfIkZKEMahOglCh15OMYsbA+vrS8do8JBilgb2\n" +
+ "AAABehHLqcEAAAQDAEcwRQIhANh++zJa9AE4U0DsHIFq6bW40b1OfGfH8uUdmjEZ\n" +
+ "s1jzAiBIRtJeFVmobSnbFKlOr8BGfD2L/hg1rkAgJlKY5oFShgB2ACl5vvCeOTkh\n" +
+ "8FZzn2Old+W+V32cYAr4+U1dJlwlXceEAAABehHLqZ4AAAQDAEcwRQIhAOZDfvU8\n" +
+ "Hz80I6Iyj2rv8+yWBVq1XVixI8bMykdCO6ADAiAWj8cJ9g1zxko4dJu8ouJf+Pwl\n" +
+ "0bbhhuJHhy/f5kiaszANBgkqhkiG9w0BAQsFAAOCAQEAlkdB7FZtVQz39TDNKR4u\n" +
+ "I8VQsTH5n4Kg+zVc0pptI7HGUWtp5PjBAEsvJ/G/NQXsjVflQaNPRRd7KNZycZL1\n" +
+ "jls6GdVoWVno6O5aLS7cCnb0tTlb8srhb9vdLZkSoCVCZLVjik5s2TLfpLsBKrTP\n" +
+ "leVY3n9TBZH+vyKLHt4WHR23Z+74xDsuXunoPGXQVV8ymqTtfohaoM19jP99vjY7\n" +
+ "DL/289XjMSfyPFqlpU4JDM7lY/kJSKB/C4eQglT8Sgm0h/kj5hdT2uMJBIQZIJVv\n" +
+ "241fAVUPgrYAESOMm2TVA9r1OzeoUNlKw+e3+vjTR6sfDDp/iRKcEVQX4u9+CxZp\n" +
+ "9g==\n-----END CERTIFICATE-----";
+ DirectDownloadCertificateResponse response = new DirectDownloadCertificateResponse();
+ helper.handleCertificateResponse(certStr, response);
+ assertEquals("3", response.getVersion());
+ assertEquals("CN=*.apache.org", response.getSubject());
+ }
}
diff --git a/test/integration/smoke/test_direct_download.py b/test/integration/smoke/test_direct_download.py
index b894f08934b..6570bb9f0b3 100644
--- a/test/integration/smoke/test_direct_download.py
+++ b/test/integration/smoke/test_direct_download.py
@@ -18,7 +18,7 @@
"""
# Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase
-from marvin.lib.utils import (cleanup_resources)
+from marvin.lib.utils import (cleanup_resources, validateList)
from marvin.lib.base import (ServiceOffering,
NetworkOffering,
Network,
@@ -28,7 +28,8 @@ from marvin.lib.base import (ServiceOffering,
from marvin.lib.common import (get_pod,
get_zone)
from nose.plugins.attrib import attr
-from marvin.cloudstackAPI import (uploadTemplateDirectDownloadCertificate, revokeTemplateDirectDownloadCertificate)
+from marvin.cloudstackAPI import (uploadTemplateDirectDownloadCertificate, revokeTemplateDirectDownloadCertificate,
+ listTemplateDirectDownloadCertificates)
from marvin.lib.decoratorGenerators import skipTestIf
import uuid
@@ -136,9 +137,13 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
except Exception as e:
self.fail("Valid certificate must be uploaded")
+ cmd = listTemplateDirectDownloadCertificates.listTemplateDirectDownloadCertificatesCmd()
+ certs = self.apiclient.listTemplateDirectDownloadCertificates(cmd)
+ validateList(certs)
+ cert = certs[0]
+
revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd()
- revokecmd.hypervisor = self.hypervisor
- revokecmd.name = cmd.name
+ revokecmd.id = cert.id
revokecmd.zoneid = self.zone.id
try:
@@ -149,6 +154,7 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
return
+
class TestDirectDownloadTemplates(cloudstackTestCase):
@classmethod