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 2019/07/18 05:39:13 UTC
[cloudstack] branch master updated: KVM: Enhancements for direct
download feature (#3374)
This is an automated email from the ASF dual-hosted git repository.
nvazquez pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/master by this push:
new a1a9fb8 KVM: Enhancements for direct download feature (#3374)
a1a9fb8 is described below
commit a1a9fb8977532f9d43c78b785d0bf3e9c53fc66e
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Thu Jul 18 02:39:00 2019 -0300
KVM: Enhancements for direct download feature (#3374)
* Add revoke certificates API
* Add background task to sync certificates
* Fix marvin test and revoke certificate
* Fix certificate sent to hypervisor was missing headers
* Fix background task for uploading certificates to hosts
---
...evokeTemplateDirectDownloadCertificateCmd.java} | 185 ++++++++--------
...UploadTemplateDirectDownloadCertificateCmd.java | 12 +-
...Manager.java => DirectDownloadCertificate.java} | 14 +-
.../direct/download/DirectDownloadManager.java | 16 +-
.../RevokeDirectDownloadCertificateCommand.java | 30 ++-
.../src/main/java/com/cloud/host/dao/HostDao.java | 2 +
.../main/java/com/cloud/host/dao/HostDaoImpl.java | 10 +
.../download/DirectDownloadCertificateDao.java | 12 +-
.../download/DirectDownloadCertificateDaoImpl.java | 53 +++++
.../DirectDownloadCertificateHostMapDao.java | 11 +-
.../DirectDownloadCertificateHostMapDaoImpl.java | 48 +++++
.../DirectDownloadCertificateHostMapVO.java | 84 ++++++++
.../download/DirectDownloadCertificateVO.java | 119 +++++++++++
.../spring-engine-schema-core-daos-context.xml | 2 +
.../resources/META-INF/db/schema-41200to41300.sql | 27 +++
.../direct/download/DirectDownloadService.java | 7 +-
...virtRevokeDirectDownloadCertificateWrapper.java | 106 ++++++++++
.../com/cloud/server/ManagementServerImpl.java | 2 +
.../direct/download/DirectDownloadManagerImpl.java | 232 +++++++++++++++++++--
test/integration/smoke/test_direct_download.py | 15 +-
20 files changed, 853 insertions(+), 134 deletions(-)
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/RevokeTemplateDirectDownloadCertificateCmd.java
old mode 100755
new mode 100644
similarity index 56%
copy from api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
copy to api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java
index c93fca2..ef9fa8b
--- 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/RevokeTemplateDirectDownloadCertificateCmd.java
@@ -1,87 +1,98 @@
-// 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 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.Parameter;
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.api.ApiErrorCode;
-import org.apache.cloudstack.api.response.SuccessResponse;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.direct.download.DirectDownloadManager;
-import org.apache.log4j.Logger;
-
-import javax.inject.Inject;
-
-@APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME,
- description = "Upload a certificate for HTTPS direct template download on KVM hosts",
- responseObject = SuccessResponse.class,
- requestHasSensitiveInfo = true,
- responseHasSensitiveInfo = true,
- since = "4.11.0",
- authorized = {RoleType.Admin})
-public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
-
- @Inject
- DirectDownloadManager directDownloadManager;
-
- private static final Logger LOG = Logger.getLogger(UploadTemplateDirectDownloadCertificateCmd.class);
- public static final String APINAME = "uploadTemplateDirectDownloadCertificate";
-
- @Parameter(name = ApiConstants.CERTIFICATE, type = BaseCmd.CommandType.STRING, required = true, length = 65535,
- description = "SSL certificate")
- private String certificate;
-
- @Parameter(name = ApiConstants.NAME , type = BaseCmd.CommandType.STRING, required = true,
- description = "Name for the uploaded certificate")
- private String name;
-
- @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true, description = "Hypervisor type")
- private String hypervisor;
-
- @Override
- public void execute() {
- if (!hypervisor.equalsIgnoreCase("kvm")) {
- throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only");
- }
-
- try {
- LOG.debug("Uploading certificate " + name + " to agents for Direct Download");
- boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor);
- SuccessResponse response = new SuccessResponse(getCommandName());
- response.setSuccess(result);
- setResponseObject(response);
- } catch (Exception e) {
- throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
- }
- }
-
- @Override
- public String getCommandName() {
- return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
- }
-
- @Override
- public long getEntityOwnerId() {
- return CallContext.current().getCallingAccount().getId();
- }
-}
-
-
+//
+// 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.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.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",
+ authorized = {RoleType.Admin})
+public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
+
+ @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.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")) {
+ 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());
+ }
+ }
+
+ @Override
+ public String getCommandName() {
+ return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+ }
+
+ @Override
+ 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/UploadTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java
index c93fca2..223f20b 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
@@ -23,7 +23,9 @@ 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.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;
@@ -56,6 +58,14 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
@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 upload 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() {
if (!hypervisor.equalsIgnoreCase("kvm")) {
@@ -64,7 +74,7 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
try {
LOG.debug("Uploading certificate " + name + " to agents for Direct Download");
- boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor);
+ boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
setResponseObject(response);
diff --git a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
similarity index 72%
copy from api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
copy to api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
index b3f0841..6227c26 100644
--- a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
+++ b/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificate.java
@@ -14,12 +14,16 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
-
package org.apache.cloudstack.direct.download;
-import com.cloud.utils.component.PluggableService;
-import org.apache.cloudstack.framework.agent.direct.download.DirectDownloadService;
+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 DirectDownloadManager extends DirectDownloadService, PluggableService {
+ String getCertificate();
+ String getAlias();
+ Hypervisor.HypervisorType getHypervisorType();
-}
+}
\ No newline at end of file
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 b3f0841..d627ffa 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
@@ -19,7 +19,21 @@ package org.apache.cloudstack.direct.download;
import com.cloud.utils.component.PluggableService;
import org.apache.cloudstack.framework.agent.direct.download.DirectDownloadService;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
-public interface DirectDownloadManager extends DirectDownloadService, PluggableService {
+public interface DirectDownloadManager extends DirectDownloadService, PluggableService, Configurable {
+ ConfigKey<Long> DirectDownloadCertificateUploadInterval = new ConfigKey<>("Advanced", Long.class,
+ "direct.download.certificate.background.task.interval",
+ "0",
+ "This interval (in hours) controls a background task to sync hosts within enabled zones " +
+ "missing uploaded certificates for direct download." +
+ "Only certificates which have not been revoked from hosts are uploaded",
+ false);
+
+ /**
+ * Revoke direct download certificate with alias 'alias' from hosts of hypervisor type 'hypervisor'
+ */
+ boolean revokeCertificateAlias(String certificateAlias, String hypervisor, Long zoneId, Long hostId);
}
diff --git a/framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java b/core/src/main/java/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java
similarity index 62%
copy from framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
copy to core/src/main/java/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java
index f3153e3..b0eb986 100644
--- a/framework/direct-download/src/main/java/org/apache/cloudstack/framework/agent/direct/download/DirectDownloadService.java
+++ b/core/src/main/java/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java
@@ -1,3 +1,4 @@
+//
// 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
@@ -14,18 +15,25 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
+//
+package org.apache.cloudstack.agent.directdownload;
+
+import com.cloud.agent.api.Command;
+
+public class RevokeDirectDownloadCertificateCommand extends Command {
-package org.apache.cloudstack.framework.agent.direct.download;
+ private String certificateAlias;
-public interface DirectDownloadService {
+ public RevokeDirectDownloadCertificateCommand(final String alias) {
+ this.certificateAlias = alias;
+ }
- /**
- * Download template/ISO into poolId bypassing secondary storage. Download performed by hostId
- */
- void downloadTemplate(long templateId, long poolId, long hostId);
+ public String getCertificateAlias() {
+ return certificateAlias;
+ }
- /**
- * Upload client certificate to each running host
- */
- boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor);
-}
+ @Override
+ public boolean executeInSequence() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java
index 1fca86c..dd45c09 100644
--- a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java
+++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java
@@ -107,4 +107,6 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
* Side note: this method is currently only used in XenServerGuru; therefore, it was designed to meet XenServer deployment scenarios requirements.
*/
HostVO findHostInZoneToExecuteCommand(long zoneId, HypervisorType hypervisorType);
+
+ List<HostVO> listAllHostsUpByZoneAndHypervisor(long zoneId, HypervisorType hypervisorType);
}
diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
index 8c8c082..71f0aef 100644
--- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
+import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@@ -1190,6 +1191,15 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
}
}
+ @Override
+ public List<HostVO> listAllHostsUpByZoneAndHypervisor(long zoneId, HypervisorType hypervisorType) {
+ return listByDataCenterIdAndHypervisorType(zoneId, hypervisorType)
+ .stream()
+ .filter(x -> x.getStatus().equals(Status.Up) &&
+ x.getType() == Host.Type.Routing)
+ .collect(Collectors.toList());
+ }
+
private ResultSet executeSqlGetResultsetForMethodFindHostInZoneToExecuteCommand(HypervisorType hypervisorType, long zoneId, TransactionLegacy tx, String sql) throws SQLException {
PreparedStatement pstmt = tx.prepareAutoCloseStatement(sql);
pstmt.setString(1, Objects.toString(hypervisorType));
diff --git a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateDao.java
similarity index 69%
copy from api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
copy to engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateDao.java
index b3f0841..69f79e0 100644
--- a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateDao.java
@@ -14,12 +14,14 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
-
package org.apache.cloudstack.direct.download;
-import com.cloud.utils.component.PluggableService;
-import org.apache.cloudstack.framework.agent.direct.download.DirectDownloadService;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.utils.db.GenericDao;
-public interface DirectDownloadManager extends DirectDownloadService, PluggableService {
+import java.util.List;
-}
+public interface DirectDownloadCertificateDao extends GenericDao<DirectDownloadCertificateVO, Long> {
+ DirectDownloadCertificateVO findByAlias(String alias, Hypervisor.HypervisorType hypervisorType, long zoneId);
+ List<DirectDownloadCertificateVO> listByZone(long zoneId);
+}
\ No newline at end of file
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateDaoImpl.java
new file mode 100644
index 0000000..a936cbb
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateDaoImpl.java
@@ -0,0 +1,53 @@
+// 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.direct.download;
+
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+import java.util.List;
+
+public class DirectDownloadCertificateDaoImpl extends GenericDaoBase<DirectDownloadCertificateVO, Long> implements DirectDownloadCertificateDao {
+
+ private final SearchBuilder<DirectDownloadCertificateVO> certificateSearchBuilder;
+
+ public DirectDownloadCertificateDaoImpl() {
+ certificateSearchBuilder = createSearchBuilder();
+ certificateSearchBuilder.and("alias", certificateSearchBuilder.entity().getAlias(), SearchCriteria.Op.EQ);
+ certificateSearchBuilder.and("hypervisor_type", certificateSearchBuilder.entity().getHypervisorType(), SearchCriteria.Op.EQ);
+ certificateSearchBuilder.and("zone_id", certificateSearchBuilder.entity().getZoneId(), SearchCriteria.Op.EQ);
+ certificateSearchBuilder.done();
+ }
+
+ @Override
+ public DirectDownloadCertificateVO findByAlias(String alias, Hypervisor.HypervisorType hypervisorType, long zoneId) {
+ SearchCriteria<DirectDownloadCertificateVO> sc = certificateSearchBuilder.create();
+ sc.setParameters("alias", alias);
+ sc.setParameters("hypervisor_type", hypervisorType);
+ sc.setParameters("zone_id", zoneId);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public List<DirectDownloadCertificateVO> listByZone(long zoneId) {
+ SearchCriteria<DirectDownloadCertificateVO> sc = certificateSearchBuilder.create();
+ sc.setParameters("zone_id", zoneId);
+ return listBy(sc);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java
similarity index 70%
copy from api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
copy to engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java
index b3f0841..e119b1d 100644
--- a/api/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManager.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDao.java
@@ -14,12 +14,13 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
-
package org.apache.cloudstack.direct.download;
-import com.cloud.utils.component.PluggableService;
-import org.apache.cloudstack.framework.agent.direct.download.DirectDownloadService;
+import com.cloud.utils.db.GenericDao;
-public interface DirectDownloadManager extends DirectDownloadService, PluggableService {
+import java.util.List;
-}
+public interface DirectDownloadCertificateHostMapDao extends GenericDao<DirectDownloadCertificateHostMapVO, Long> {
+ DirectDownloadCertificateHostMapVO findByCertificateAndHost(long certificateId, long hostId);
+ List<DirectDownloadCertificateHostMapVO> listByCertificateId(long certificateId);
+}
\ 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
new file mode 100644
index 0000000..7a0b732
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapDaoImpl.java
@@ -0,0 +1,48 @@
+// 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.direct.download;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+import java.util.List;
+
+public class DirectDownloadCertificateHostMapDaoImpl extends GenericDaoBase<DirectDownloadCertificateHostMapVO, Long> implements DirectDownloadCertificateHostMapDao {
+ private final SearchBuilder<DirectDownloadCertificateHostMapVO> mapSearchBuilder;
+
+ public DirectDownloadCertificateHostMapDaoImpl() {
+ mapSearchBuilder = createSearchBuilder();
+ mapSearchBuilder.and("certificate_id", mapSearchBuilder.entity().getCertificateId(), SearchCriteria.Op.EQ);
+ mapSearchBuilder.and("host_id", mapSearchBuilder.entity().getHostId(), SearchCriteria.Op.EQ);
+ mapSearchBuilder.done();
+ }
+ @Override
+ public DirectDownloadCertificateHostMapVO findByCertificateAndHost(long certificateId, long hostId) {
+ SearchCriteria<DirectDownloadCertificateHostMapVO> sc = mapSearchBuilder.create();
+ sc.setParameters("certificate_id", certificateId);
+ sc.setParameters("host_id", hostId);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public List<DirectDownloadCertificateHostMapVO> listByCertificateId(long certificateId) {
+ SearchCriteria<DirectDownloadCertificateHostMapVO> sc = mapSearchBuilder.create();
+ sc.setParameters("certificate_id", certificateId);
+ 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
new file mode 100644
index 0000000..db5faf6
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java
@@ -0,0 +1,84 @@
+// 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.direct.download;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "direct_download_certificate_host_map")
+public class DirectDownloadCertificateHostMapVO {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "host_id")
+ private Long hostId;
+
+ @Column(name = "certificate_id")
+ private Long certificateId;
+
+ @Column(name = "revoked")
+ private Boolean revoked;
+
+ public DirectDownloadCertificateHostMapVO() {
+ }
+
+ public DirectDownloadCertificateHostMapVO(Long certificateId, Long hostId) {
+ this.certificateId = certificateId;
+ this.hostId = hostId;
+ this.revoked = false;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getHostId() {
+ return hostId;
+ }
+
+ public void setHostId(Long hostId) {
+ this.hostId = hostId;
+ }
+
+ public Long getCertificateId() {
+ return certificateId;
+ }
+
+ public void setCertificateId(Long certificateId) {
+ this.certificateId = certificateId;
+ }
+
+ public Boolean isRevoked() {
+ return revoked;
+ }
+
+ public void setRevoked(Boolean revoked) {
+ this.revoked = revoked;
+ }
+}
\ No newline at end of file
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java
new file mode 100644
index 0000000..0b147d7
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java
@@ -0,0 +1,119 @@
+// 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.direct.download;
+
+import com.cloud.hypervisor.Hypervisor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.UUID;
+
+@Entity
+@Table(name = "direct_download_certificate")
+public class DirectDownloadCertificateVO implements DirectDownloadCertificate {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "alias")
+ private String alias;
+
+ @Column(name = "certificate", length = 65535)
+ private String certificate;
+
+ @Column(name = "hypervisor_type")
+ private Hypervisor.HypervisorType hypervisorType;
+
+ @Column(name = "zone_id")
+ private Long zoneId;
+
+ public DirectDownloadCertificateVO() {
+ this.uuid = UUID.randomUUID().toString();
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public void setCertificate(String certificate) {
+ this.certificate = certificate;
+ }
+
+ public void setHypervisorType(Hypervisor.HypervisorType hypervisorType) {
+ this.hypervisorType = hypervisorType;
+ }
+
+ public DirectDownloadCertificateVO(String alias, String certificate,
+ Hypervisor.HypervisorType hypervisorType, Long zoneId) {
+ this();
+ this.alias = alias;
+ this.certificate = certificate;
+ this.hypervisorType = hypervisorType;
+ this.zoneId = zoneId;
+ }
+
+ @Override
+ public String getCertificate() {
+ return certificate;
+ }
+
+ @Override
+ public String getAlias() {
+ return alias;
+ }
+
+ @Override
+ public Hypervisor.HypervisorType getHypervisorType() {
+ return hypervisorType;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(Long zoneId) {
+ this.zoneId = zoneId;
+ }
+
+}
\ No newline at end of file
diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
index fc2a752..1cea7aa 100644
--- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
+++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
@@ -285,4 +285,6 @@
<bean id="outOfBandManagementDaoImpl" class="org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDaoImpl" />
<bean id="GuestOsDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.GuestOsDetailsDaoImpl" />
<bean id="annotationDaoImpl" class="org.apache.cloudstack.annotation.dao.AnnotationDaoImpl" />
+ <bean id="directDownloadCertificateDaoImpl" class="org.apache.cloudstack.direct.download.DirectDownloadCertificateDaoImpl" />
+ <bean id="directDownloadCertificateHostMapDaoImpl" class="org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMapDaoImpl" />
</beans>
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql b/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
index 8b60592..904e76e 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
@@ -359,3 +359,30 @@ CREATE VIEW `cloud`.`project_view` AS
`cloud`.`account` ON account.id = project_account.account_id
left join
`cloud`.`project_account` pacct ON projects.id = pacct.project_id;
+
+-- KVM: Add background task to upload certificates for direct download
+CREATE TABLE `cloud`.`direct_download_certificate` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `uuid` varchar(40) NOT NULL,
+ `alias` varchar(255) NOT NULL,
+ `certificate` text NOT NULL,
+ `hypervisor_type` varchar(45) NOT NULL,
+ `zone_id` bigint(20) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `i_direct_download_certificate_alias` (`alias`),
+ KEY `fk_direct_download_certificate__zone_id` (`zone_id`),
+ CONSTRAINT `fk_direct_download_certificate__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`direct_download_certificate_host_map` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `certificate_id` bigint(20) unsigned NOT NULL,
+ `host_id` bigint(20) unsigned NOT NULL,
+ `revoked` int(1) NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ KEY `fk_direct_download_certificate_host_map__host_id` (`host_id`),
+ KEY `fk_direct_download_certificate_host_map__certificate_id` (`certificate_id`),
+ CONSTRAINT `fk_direct_download_certificate_host_map__host_id` FOREIGN KEY (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `fk_direct_download_certificate_host_map__certificate_id` FOREIGN KEY (`certificate_id`) REFERENCES `direct_download_certificate` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
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 f3153e3..ed7bbd7 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
@@ -27,5 +27,10 @@ public interface DirectDownloadService {
/**
* Upload client certificate to each running host
*/
- boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor);
+ 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);
}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
new file mode 100644
index 0000000..e942dcb
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
@@ -0,0 +1,106 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
+import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.log4j.Logger;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+@ResourceWrapper(handles = RevokeDirectDownloadCertificateCommand.class)
+public class LibvirtRevokeDirectDownloadCertificateWrapper extends CommandWrapper<RevokeDirectDownloadCertificateCommand, Answer, LibvirtComputingResource> {
+
+ private static final Logger s_logger = Logger.getLogger(LibvirtRevokeDirectDownloadCertificateWrapper.class);
+
+ /**
+ * Retrieve agent.properties file
+ */
+ private File getAgentPropertiesFile() throws FileNotFoundException {
+ final File agentFile = PropertiesUtil.findConfigFile("agent.properties");
+ if (agentFile == null) {
+ throw new FileNotFoundException("Failed to find agent.properties file");
+ }
+ return agentFile;
+ }
+
+ /**
+ * Get the property 'keystore.passphrase' value from agent.properties file
+ */
+ private String getKeystorePassword(File agentFile) {
+ String pass = null;
+ if (agentFile != null) {
+ try {
+ pass = PropertiesUtil.loadFromFile(agentFile).getProperty(KeyStoreUtils.KS_PASSPHRASE_PROPERTY);
+ } catch (IOException e) {
+ s_logger.error("Could not get 'keystore.passphrase' property value due to: " + e.getMessage());
+ }
+ }
+ return pass;
+ }
+
+ /**
+ * Get keystore path
+ */
+ private String getKeyStoreFilePath(File agentFile) {
+ return agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME;
+ }
+
+ @Override
+ public Answer execute(RevokeDirectDownloadCertificateCommand command, LibvirtComputingResource serverResource) {
+ String certificateAlias = command.getCertificateAlias();
+ try {
+ File agentFile = getAgentPropertiesFile();
+ String privatePassword = getKeystorePassword(agentFile);
+ if (isBlank(privatePassword)) {
+ return new Answer(command, false, "No password found for keystore: " + KeyStoreUtils.KS_FILENAME);
+ }
+
+ final String keyStoreFile = getKeyStoreFilePath(agentFile);
+
+ String checkCmd = String.format("keytool -list -alias %s -keystore %s -storepass %s",
+ certificateAlias, keyStoreFile, privatePassword);
+ int existsCmdResult = Script.runSimpleBashScriptForExitValue(checkCmd);
+ if (existsCmdResult == 1) {
+ s_logger.error("Certificate alias " + certificateAlias + " does not exist, no need to revoke it");
+ } else {
+ String revokeCmd = String.format("keytool -delete -alias %s -keystore %s -storepass %s",
+ certificateAlias, keyStoreFile, privatePassword);
+ s_logger.debug("Revoking certificate alias " + certificateAlias + " from keystore " + keyStoreFile);
+ Script.runSimpleBashScriptForExitValue(revokeCmd);
+ }
+ } catch (FileNotFoundException | CloudRuntimeException e) {
+ s_logger.error("Error while setting up certificate " + certificateAlias, e);
+ return new Answer(command, false, e.getMessage());
+ }
+ return new Answer(command);
+ }
+}
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 4beddcc..0066a95 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -67,6 +67,7 @@ import org.apache.cloudstack.api.command.admin.config.ListDeploymentPlannersCmd;
import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd;
+import org.apache.cloudstack.api.command.admin.direct.download.RevokeTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.domain.CreateDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.DeleteDomainCmd;
@@ -3100,6 +3101,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(CreateManagementNetworkIpRangeCmd.class);
cmdList.add(DeleteManagementNetworkIpRangeCmd.class);
cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class);
+ cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListMgmtsCmd.class);
cmdList.add(GetUploadParamsForIsoCmd.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 9986093..2eb6d36 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
@@ -22,9 +22,13 @@ import static com.cloud.storage.Storage.ImageFormat;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventTypes;
import com.cloud.event.EventVO;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.OperationTimedoutException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
@@ -37,6 +41,7 @@ import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.exception.CloudRuntimeException;
import java.net.URI;
@@ -51,8 +56,12 @@ import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.Collections;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.inject.Inject;
+import javax.naming.ConfigurationException;
import com.cloud.utils.security.CertificateHelper;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
@@ -62,18 +71,24 @@ import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand;
+import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand;
import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.poll.BackgroundPollManager;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import sun.security.x509.X509CertImpl;
public class DirectDownloadManagerImpl extends ManagerBase implements DirectDownloadManager {
@@ -96,6 +111,16 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
private VMTemplatePoolDao vmTemplatePoolDao;
@Inject
private DataStoreManager dataStoreManager;
+ @Inject
+ private DirectDownloadCertificateDao directDownloadCertificateDao;
+ @Inject
+ private DirectDownloadCertificateHostMapDao directDownloadCertificateHostMapDao;
+ @Inject
+ private BackgroundPollManager backgroundPollManager;
+ @Inject
+ private DataCenterDao dataCenterDao;
+
+ protected ScheduledExecutorService executorService;
@Override
public List<Class<?>> getCommands() {
@@ -311,12 +336,8 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
/**
* Return the list of running hosts to which upload certificates for Direct Download
*/
- private List<HostVO> getRunningHostsToUploadCertificate(HypervisorType hypervisorType) {
- return hostDao.listAllHostsByType(Host.Type.Routing)
- .stream()
- .filter(x -> x.getStatus().equals(Status.Up) &&
- x.getHypervisorType().equals(hypervisorType))
- .collect(Collectors.toList());
+ private List<HostVO> getRunningHostsToUploadCertificate(Long zoneId, HypervisorType hypervisorType) {
+ return hostDao.listAllHostsUpByZoneAndHypervisor(zoneId, hypervisorType);
}
/**
@@ -365,22 +386,42 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
}
@Override
- public boolean uploadCertificateToHosts(String certificateCer, String alias, String hypervisor) {
+ public boolean 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");
}
+ List<HostVO> hosts;
+ DirectDownloadCertificateVO certificateVO;
HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
- List<HostVO> hosts = getRunningHostsToUploadCertificate(hypervisorType);
- String certificatePem = getPretifiedCertificate(certificateCer);
- certificateSanity(certificatePem);
+ if (hostId == null) {
+ hosts = getRunningHostsToUploadCertificate(zoneId, hypervisorType);
+
+ String certificatePem = getPretifiedCertificate(certificateCer);
+ certificateSanity(certificatePem);
+
+ certificateVO = directDownloadCertificateDao.findByAlias(alias, hypervisorType, zoneId);
+ if (certificateVO != null) {
+ throw new CloudRuntimeException("Certificate alias " + alias + " has been already created");
+ }
+ certificateVO = new DirectDownloadCertificateVO(alias, certificatePem, hypervisorType, zoneId);
+ directDownloadCertificateDao.persist(certificateVO);
+ } else {
+ HostVO host = hostDao.findById(hostId);
+ hosts = Collections.singletonList(host);
+ certificateVO = directDownloadCertificateDao.findByAlias(alias, hypervisorType, zoneId);
+ if (certificateVO == null) {
+ s_logger.info("Certificate must be uploaded on zone " + zoneId);
+ return false;
+ }
+ }
- s_logger.info("Attempting to upload certificate: " + alias + " to " + hosts.size() + " hosts");
+ s_logger.info("Attempting to upload certificate: " + alias + " to " + hosts.size() + " hosts on zone " + zoneId);
int hostCount = 0;
if (CollectionUtils.isNotEmpty(hosts)) {
for (HostVO host : hosts) {
- if (!uploadCertificate(certificatePem, alias, host.getId())) {
+ if (!uploadCertificate(certificateVO.getId(), host.getId())) {
String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + ")";
s_logger.error(msg);
throw new CloudRuntimeException(msg);
@@ -395,20 +436,177 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
/**
* Upload and import certificate to hostId on keystore
*/
- protected boolean uploadCertificate(String certificate, String certificateName, long hostId) {
- s_logger.debug("Uploading certificate: " + certificateName + " to host " + hostId);
- SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificate, certificateName);
+ 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();
+
+ s_logger.debug("Uploading certificate: " + certificateVO.getAlias() + " to host " + hostId);
+ SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificate, alias);
Answer answer = agentManager.easySend(hostId, cmd);
if (answer == null || !answer.getResult()) {
- String msg = "Certificate " + certificateName + " could not be added to host " + hostId;
+ 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;
}
- s_logger.info("Certificate " + certificateName + " successfully uploaded to host: " + hostId);
+
+ s_logger.info("Certificate " + alias + " successfully uploaded to host: " + hostId);
+ DirectDownloadCertificateHostMapVO map = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateId, hostId);
+ if (map != null) {
+ map.setRevoked(false);
+ directDownloadCertificateHostMapDao.update(map.getId(), map);
+ } else {
+ DirectDownloadCertificateHostMapVO mapVO = new DirectDownloadCertificateHostMapVO(certificateId, hostId);
+ directDownloadCertificateHostMapDao.persist(mapVO);
+ }
+
return true;
}
+ @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;
+ if (hostId == null) {
+ maps = directDownloadCertificateHostMapDao.listByCertificateId(certificateVO.getId());
+ } else {
+ DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.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;
+ }
+ maps = Collections.singletonList(hostMap);
+ }
+
+ 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);
+ }
+ s_logger.info("Certificate " + certificateAlias + " revoked from host " + mappingHostId);
+ map.setRevoked(true);
+ directDownloadCertificateHostMapDao.update(map.getId(), map);
+ }
+ }
+ return true;
+ }
+
+ protected boolean revokeCertificateAliasFromHost(String alias, Long hostId) {
+ RevokeDirectDownloadCertificateCommand cmd = new RevokeDirectDownloadCertificateCommand(alias);
+ try {
+ Answer answer = agentManager.send(hostId, cmd);
+ return answer != null && answer.getResult();
+ } catch (AgentUnavailableException | OperationTimedoutException e) {
+ s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+ executorService = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("DirectDownloadCertificateMonitor"));
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ executorService.shutdownNow();
+ return true;
+ }
+
+ @Override
+ public boolean start() {
+ if (DirectDownloadCertificateUploadInterval.value() > 0L) {
+ executorService.scheduleWithFixedDelay(
+ new DirectDownloadCertificateUploadBackgroundTask(this, hostDao, dataCenterDao,
+ directDownloadCertificateDao, directDownloadCertificateHostMapDao),
+ 60L, DirectDownloadCertificateUploadInterval.value(), TimeUnit.HOURS);
+ }
+ return true;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return DirectDownloadManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[]{
+ DirectDownloadCertificateUploadInterval
+ };
+ }
+
+ public static final class DirectDownloadCertificateUploadBackgroundTask extends ManagedContextRunnable {
+
+ private DirectDownloadManager directDownloadManager;
+ private HostDao hostDao;
+ private DirectDownloadCertificateDao directDownloadCertificateDao;
+ private DirectDownloadCertificateHostMapDao directDownloadCertificateHostMapDao;
+ private DataCenterDao dataCenterDao;
+
+ public DirectDownloadCertificateUploadBackgroundTask(
+ final DirectDownloadManager manager,
+ final HostDao hostDao,
+ final DataCenterDao dataCenterDao,
+ final DirectDownloadCertificateDao directDownloadCertificateDao,
+ final DirectDownloadCertificateHostMapDao directDownloadCertificateHostMapDao) {
+ this.directDownloadManager = manager;
+ this.hostDao = hostDao;
+ this.dataCenterDao = dataCenterDao;
+ this.directDownloadCertificateDao = directDownloadCertificateDao;
+ this.directDownloadCertificateHostMapDao = directDownloadCertificateHostMapDao;
+ }
+
+ @Override
+ protected void runInContext() {
+ try {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Direct Download Manager background task is running...");
+ }
+ final DateTime now = DateTime.now(DateTimeZone.UTC);
+ List<DataCenterVO> enabledZones = dataCenterDao.listEnabledZones();
+ for (DataCenterVO zone : enabledZones) {
+ List<DirectDownloadCertificateVO> zoneCertificates = directDownloadCertificateDao.listByZone(zone.getId());
+ if (CollectionUtils.isNotEmpty(zoneCertificates)) {
+ for (DirectDownloadCertificateVO certificateVO : zoneCertificates) {
+ List<HostVO> hostsToUpload = hostDao.listAllHostsUpByZoneAndHypervisor(certificateVO.getZoneId(), certificateVO.getHypervisorType());
+ if (CollectionUtils.isNotEmpty(hostsToUpload)) {
+ for (HostVO hostVO : hostsToUpload) {
+ DirectDownloadCertificateHostMapVO mapping = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostVO.getId());
+ if (mapping == null) {
+ 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());
+ s_logger.debug("Certificate " + certificateVO.getAlias() + " " +
+ (result ? "uploaded" : "could not be uploaded") +
+ " to host " + hostVO.getId());
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (final Throwable t) {
+ s_logger.error("Error trying to run Direct Download background task", t);
+ }
+ }
+ }
}
diff --git a/test/integration/smoke/test_direct_download.py b/test/integration/smoke/test_direct_download.py
index 65117f9..132deb4 100644
--- a/test/integration/smoke/test_direct_download.py
+++ b/test/integration/smoke/test_direct_download.py
@@ -27,7 +27,7 @@ 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
+from marvin.cloudstackAPI import (uploadTemplateDirectDownloadCertificate, revokeTemplateDirectDownloadCertificate)
from marvin.lib.decoratorGenerators import skipTestIf
@@ -92,6 +92,7 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
cmd.hypervisor = self.hypervisor
cmd.name = "marvin-test-verify-certs"
cmd.certificate = self.certificates["invalid"]
+ cmd.zoneid = self.zone.id
invalid_cert_uploadFails = False
expired_cert_upload_fails = False
@@ -120,17 +121,29 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
# Validate the following
# 1. Valid certificates are uploaded to hosts
+ # 2. Revoke uploaded certificate from host
cmd = uploadTemplateDirectDownloadCertificate.uploadTemplateDirectDownloadCertificateCmd()
cmd.hypervisor = self.hypervisor
cmd.name = "marvin-test-verify-certs"
cmd.certificate = self.certificates["valid"]
+ cmd.zoneid = self.zone.id
try:
self.apiclient.uploadTemplateDirectDownloadCertificate(cmd)
except Exception as e:
self.fail("Valid certificate must be uploaded")
+ revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd()
+ revokecmd.hypervisor = self.hypervisor
+ revokecmd.name = cmd.name
+ revokecmd.zoneid = self.zone.id
+
+ try:
+ self.apiclient.revokeTemplateDirectDownloadCertificate(revokecmd)
+ except Exception as e:
+ self.fail("Uploaded certificates should be revoked when needed")
+
return