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 <nicovazque...@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 <sureshkumar.anapa...@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 <sureshkumar.anapa...@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