diff --git a/.asf.yaml b/.asf.yaml
index f27b5afa425..cb957e57fd9 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -58,8 +58,6 @@ github:
- GaOrtiga
- bhouse-nexthop
- protected_branches:
-
rulesets:
- name: "Default Branch Protection"
type: branch
diff --git a/api/pom.xml b/api/pom.xml
index c80c3559345..4cdb57b6414 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -71,6 +71,11 @@
cloud-framework-direct-download
${project.version}
+
+ org.apache.cloudstack
+ cloud-framework-kms
+ ${project.version}
+
diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java
index a32e3879810..c97e916fe43 100644
--- a/api/src/main/java/com/cloud/event/EventTypes.java
+++ b/api/src/main/java/com/cloud/event/EventTypes.java
@@ -39,6 +39,8 @@ import org.apache.cloudstack.gpu.GpuCard;
import org.apache.cloudstack.gpu.GpuDevice;
import org.apache.cloudstack.gpu.VgpuProfile;
import org.apache.cloudstack.ha.HAConfig;
+import org.apache.cloudstack.kms.HSMProfile;
+import org.apache.cloudstack.kms.KMSKey;
import org.apache.cloudstack.network.BgpPeer;
import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap;
import org.apache.cloudstack.quota.QuotaTariff;
@@ -46,7 +48,7 @@ import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.usage.Usage;
-import org.apache.cloudstack.vm.schedule.VMSchedule;
+import org.apache.cloudstack.schedule.ResourceSchedule;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterGuestIpv6Prefix;
@@ -128,17 +130,18 @@ public class EventTypes {
public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE";
public static final String EVENT_VM_RECOVER = "VM.RECOVER";
- // VM Schedule
- public static final String EVENT_VM_SCHEDULE_CREATE = "VM.SCHEDULE.CREATE";
- public static final String EVENT_VM_SCHEDULE_UPDATE = "VM.SCHEDULE.UPDATE";
- public static final String EVENT_VM_SCHEDULE_DELETE = "VM.SCHEDULE.DELETE";
-
+ // VM Schedule action-execution events (fired when a scheduled action runs).
public static final String EVENT_VM_SCHEDULE_START = "VM.SCHEDULE.START";
public static final String EVENT_VM_SCHEDULE_STOP = "VM.SCHEDULE.STOP";
public static final String EVENT_VM_SCHEDULE_REBOOT = "VM.SCHEDULE.REBOOT";
public static final String EVENT_VM_SCHEDULE_FORCE_STOP = "VM.SCHEDULE.FORCE.STOP";
public static final String EVENT_VM_SCHEDULE_FORCE_REBOOT = "VM.SCHEDULE.FORCE.REBOOT";
+ // Generic Resource Schedule CRUD events (apply to all resource types).
+ public static final String EVENT_SCHEDULE_CREATE = "SCHEDULE.CREATE";
+ public static final String EVENT_SCHEDULE_UPDATE = "SCHEDULE.UPDATE";
+ public static final String EVENT_SCHEDULE_DELETE = "SCHEDULE.DELETE";
+
// Domain Router
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY";
@@ -274,6 +277,20 @@ public class EventTypes {
public static final String EVENT_CA_CERTIFICATE_REVOKE = "CA.CERTIFICATE.REVOKE";
public static final String EVENT_CA_CERTIFICATE_PROVISION = "CA.CERTIFICATE.PROVISION";
+ // KMS (Key Management Service) events
+ public static final String EVENT_KMS_KEY_WRAP = "KMS.KEY.WRAP";
+ public static final String EVENT_KMS_KEY_UNWRAP = "KMS.KEY.UNWRAP";
+ public static final String EVENT_KMS_KEY_CREATE = "KMS.KEY.CREATE";
+ public static final String EVENT_KMS_KEY_UPDATE = "KMS.KEY.UPDATE";
+ public static final String EVENT_KMS_KEY_ROTATE = "KMS.KEY.ROTATE";
+ public static final String EVENT_KMS_KEY_DELETE = "KMS.KEY.DELETE";
+ public static final String EVENT_VOLUME_MIGRATE_TO_KMS = "VOLUME.MIGRATE.TO.KMS";
+
+ // HSM Profile events
+ public static final String EVENT_HSM_PROFILE_CREATE = "HSM.PROFILE.CREATE";
+ public static final String EVENT_HSM_PROFILE_UPDATE = "HSM.PROFILE.UPDATE";
+ public static final String EVENT_HSM_PROFILE_DELETE = "HSM.PROFILE.DELETE";
+
// Account events
public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE";
public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE";
@@ -679,6 +696,7 @@ public class EventTypes {
public static final String EVENT_AUTOSCALEVMGROUP_DISABLE = "AUTOSCALEVMGROUP.DISABLE";
public static final String EVENT_AUTOSCALEVMGROUP_SCALEDOWN = "AUTOSCALEVMGROUP.SCALEDOWN";
public static final String EVENT_AUTOSCALEVMGROUP_SCALEUP = "AUTOSCALEVMGROUP.SCALEUP";
+ public static final String EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE = "AUTOSCALEVMGROUP.SCHEDULE.UPDATE";
public static final String EVENT_BAREMETAL_DHCP_SERVER_ADD = "PHYSICAL.DHCP.ADD";
public static final String EVENT_BAREMETAL_DHCP_SERVER_DELETE = "PHYSICAL.DHCP.DELETE";
@@ -902,15 +920,18 @@ public class EventTypes {
entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class);
entityEventDetails.put(EVENT_VM_UNMANAGE, VirtualMachine.class);
- // VMSchedule
- entityEventDetails.put(EVENT_VM_SCHEDULE_CREATE, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_DELETE, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_UPDATE, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_START, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, VMSchedule.class);
- entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, VMSchedule.class);
+ // VMSchedule action-execution events
+ entityEventDetails.put(EVENT_VM_SCHEDULE_START, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE, ResourceSchedule.class);
+
+ // Generic Resource Schedule
+ entityEventDetails.put(EVENT_SCHEDULE_CREATE, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_SCHEDULE_UPDATE, ResourceSchedule.class);
+ entityEventDetails.put(EVENT_SCHEDULE_DELETE, ResourceSchedule.class);
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);
@@ -1029,6 +1050,20 @@ public class EventTypes {
entityEventDetails.put(EVENT_VOLUME_RECOVER, Volume.class);
entityEventDetails.put(EVENT_VOLUME_CHANGE_DISK_OFFERING, Volume.class);
+ // KMS Key Events
+ entityEventDetails.put(EVENT_KMS_KEY_CREATE, KMSKey.class);
+ entityEventDetails.put(EVENT_KMS_KEY_UPDATE, KMSKey.class);
+ entityEventDetails.put(EVENT_KMS_KEY_UNWRAP, KMSKey.class);
+ entityEventDetails.put(EVENT_KMS_KEY_WRAP, KMSKey.class);
+ entityEventDetails.put(EVENT_KMS_KEY_DELETE, KMSKey.class);
+ entityEventDetails.put(EVENT_KMS_KEY_ROTATE, KMSKey.class);
+ entityEventDetails.put(EVENT_VOLUME_MIGRATE_TO_KMS, KMSKey.class);
+
+ // HSM Profile Events
+ entityEventDetails.put(EVENT_HSM_PROFILE_CREATE, HSMProfile.class);
+ entityEventDetails.put(EVENT_HSM_PROFILE_UPDATE, HSMProfile.class);
+ entityEventDetails.put(EVENT_HSM_PROFILE_DELETE, HSMProfile.class);
+
// Domains
entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class);
entityEventDetails.put(EVENT_DOMAIN_DELETE, Domain.class);
diff --git a/api/src/main/java/com/cloud/host/Host.java b/api/src/main/java/com/cloud/host/Host.java
index 8b14cfd3a39..c110e4ca94e 100644
--- a/api/src/main/java/com/cloud/host/Host.java
+++ b/api/src/main/java/com/cloud/host/Host.java
@@ -63,6 +63,7 @@ public interface Host extends StateObject, Identity, Partition, HAResour
String HOST_OVFTOOL_VERSION = "host.ovftool.version";
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
String HOST_SSH_PORT = "host.ssh.port";
+ String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count";
String GUEST_OS_CATEGORY_ID = "guest.os.category.id";
String GUEST_OS_RULE = "guest.os.rule";
diff --git a/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java b/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java
index 12dcf423e34..197565a1fcc 100644
--- a/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java
+++ b/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java
@@ -23,6 +23,7 @@ public class DiskOfferingInfo {
private Long _size;
private Long _minIops;
private Long _maxIops;
+ private Long _kmsKeyId;
public DiskOfferingInfo() {
}
@@ -38,6 +39,14 @@ public class DiskOfferingInfo {
_maxIops = maxIops;
}
+ public DiskOfferingInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long kmsKeyId) {
+ _diskOffering = diskOffering;
+ _size = size;
+ _minIops = minIops;
+ _maxIops = maxIops;
+ _kmsKeyId = kmsKeyId;
+ }
+
public void setDiskOffering(DiskOffering diskOffering) {
_diskOffering = diskOffering;
}
@@ -69,4 +78,12 @@ public class DiskOfferingInfo {
public Long getMaxIops() {
return _maxIops;
}
+
+ public void setKmsKeyId(Long kmsKeyId) {
+ _kmsKeyId = kmsKeyId;
+ }
+
+ public Long getKmsKeyId() {
+ return _kmsKeyId;
+ }
}
diff --git a/api/src/main/java/com/cloud/storage/Volume.java b/api/src/main/java/com/cloud/storage/Volume.java
index c7fbdb0a544..c7a13d5780d 100644
--- a/api/src/main/java/com/cloud/storage/Volume.java
+++ b/api/src/main/java/com/cloud/storage/Volume.java
@@ -275,6 +275,14 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
void setPassphraseId(Long id);
+ Long getKmsKeyId();
+
+ void setKmsKeyId(Long id);
+
+ Long getKmsWrappedKeyId();
+
+ void setKmsWrappedKeyId(Long id);
+
String getEncryptFormat();
void setEncryptFormat(String encryptFormat);
diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java
index 43ead8d287b..d287cc335ee 100644
--- a/api/src/main/java/com/cloud/storage/VolumeApiService.java
+++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java
@@ -72,7 +72,7 @@ public interface VolumeApiService {
Volume allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationException;
Volume allocVolume(long ownerId, Long zoneId, Long diskOfferingId, Long vmId, Long snapshotId, String name,
- Long cmdSize, Boolean displayVolume, Long cmdMinIops, Long cmdMaxIops, String customId)
+ Long cmdSize, Boolean displayVolume, Long cmdMinIops, Long cmdMaxIops, String customId, Long kmsKeyId)
throws ResourceAllocationException;
/**
diff --git a/api/src/main/java/com/cloud/vm/DiskProfile.java b/api/src/main/java/com/cloud/vm/DiskProfile.java
index 971ebde496e..766573d4d40 100644
--- a/api/src/main/java/com/cloud/vm/DiskProfile.java
+++ b/api/src/main/java/com/cloud/vm/DiskProfile.java
@@ -82,7 +82,7 @@ public class DiskProfile {
null);
this.hyperType = hyperType;
this.provisioningType = offering.getProvisioningType();
- this.requiresEncryption = offering.getEncrypt() || vol.getPassphraseId() != null;
+ this.requiresEncryption = offering.getEncrypt() || vol.getPassphraseId() != null || vol.getKmsKeyId() != null;
}
public DiskProfile(DiskProfile dp) {
diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java
index 67aa0534a5f..ffa00734d57 100644
--- a/api/src/main/java/com/cloud/vm/UserVmService.java
+++ b/api/src/main/java/com/cloud/vm/UserVmService.java
@@ -229,7 +229,7 @@ public interface UserVmService {
String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard,
List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap,
Map dataDiskTemplateToDiskOfferingMap,
- Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException,
+ Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
/**
@@ -305,7 +305,7 @@ public interface UserVmService {
List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskInfoList, String group, HypervisorType hypervisor,
HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard,
List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap,
- Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
+ Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
/**
* Creates a User VM in Advanced Zone (Security Group feature is disabled)
@@ -377,7 +377,7 @@ public interface UserVmService {
String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskInfoList, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData,
Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList,
Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap,
- Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot)
+ Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, Volume volume, Snapshot snapshot)
throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
diff --git a/api/src/main/java/com/cloud/vm/VmDiskInfo.java b/api/src/main/java/com/cloud/vm/VmDiskInfo.java
index b8779a8d77c..97683e8397f 100644
--- a/api/src/main/java/com/cloud/vm/VmDiskInfo.java
+++ b/api/src/main/java/com/cloud/vm/VmDiskInfo.java
@@ -33,6 +33,11 @@ public class VmDiskInfo extends DiskOfferingInfo {
_deviceId = deviceId;
}
+ public VmDiskInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long deviceId, Long kmsKeyId) {
+ super(diskOffering, size, minIops, maxIops, kmsKeyId);
+ _deviceId = deviceId;
+ }
+
public Long getDeviceId() {
return _deviceId;
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
index e2ebb242cbf..2aa97b65a3d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
@@ -89,7 +89,9 @@ public enum ApiCommandResourceType {
KubernetesSupportedVersion(null),
SharedFS(org.apache.cloudstack.storage.sharedfs.SharedFS.class),
Extension(org.apache.cloudstack.extension.Extension.class),
- ExtensionCustomAction(org.apache.cloudstack.extension.ExtensionCustomAction.class);
+ ExtensionCustomAction(org.apache.cloudstack.extension.ExtensionCustomAction.class),
+ KmsKey(org.apache.cloudstack.kms.KMSKey.class),
+ HsmProfile(org.apache.cloudstack.kms.HSMProfile.class);
private final Class> clazz;
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 f10b3f0a390..2413760d69d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -202,6 +202,7 @@ public class ApiConstants {
public static final String UTILIZATION = "utilization";
public static final String DRIVER = "driver";
public static final String ROOT_DISK_SIZE = "rootdisksize";
+ public static final String ROOT_DISK_KMS_KEY_ID = "rootdiskkmskeyid";
public static final String DHCP_OPTIONS_NETWORK_LIST = "dhcpoptionsnetworklist";
public static final String DHCP_OPTIONS = "dhcpoptions";
public static final String DHCP_PREFIX = "dhcp:";
@@ -886,7 +887,14 @@ public class ApiConstants {
public static final String ITEMS = "items";
public static final String SORT_BY = "sortby";
public static final String CHANGE_CIDR = "changecidr";
+ public static final String HSM_PROFILE = "hsmprofile";
+ public static final String HSM_PROFILE_ID = "hsmprofileid";
public static final String PURPOSE = "purpose";
+ public static final String KMS_KEY = "kmskey";
+ public static final String KMS_KEY_ID = "kmskeyid";
+ public static final String KMS_KEY_VERSION = "kmskeyversion";
+ public static final String KEK_LABEL = "keklabel";
+ public static final String KEY_BITS = "keybits";
public static final String IS_TAGGED = "istagged";
public static final String INSTANCE_NAME = "instancename";
public static final String CONSIDER_LAST_HOST = "considerlasthost";
@@ -1343,8 +1351,10 @@ public class ApiConstants {
public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement";
public static final String VNF_CIDR_LIST = "vnfcidrlist";
+ public static final String AUTHORIZE_URL = "authorizeurl";
public static final String CLIENT_ID = "clientid";
public static final String REDIRECT_URI = "redirecturi";
+ public static final String TOKEN_URL = "tokenurl";
public static final String IS_TAG_A_RULE = "istagarule";
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 b0738cf78e1..6e880c89432 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
@@ -281,7 +281,8 @@ public interface ResponseGenerator {
List createUserVmResponse(ResponseView view, String objectName, UserVm... userVms);
- List createUserVmResponse(ResponseView view, String objectName, EnumSet details, UserVm... userVms);
+ List createUserVmResponse(ResponseView view, String objectName, EnumSet details,
+ UserVm... userVms);
SystemVmResponse createSystemVmResponse(VirtualMachine systemVM);
@@ -307,11 +308,13 @@ public interface ResponseGenerator {
LoadBalancerResponse createLoadBalancerResponse(LoadBalancer loadBalancer);
- LBStickinessResponse createLBStickinessPolicyResponse(List extends StickinessPolicy> stickinessPolicies, LoadBalancer lb);
+ LBStickinessResponse createLBStickinessPolicyResponse(List extends StickinessPolicy> stickinessPolicies,
+ LoadBalancer lb);
LBStickinessResponse createLBStickinessPolicyResponse(StickinessPolicy stickinessPolicy, LoadBalancer lb);
- LBHealthCheckResponse createLBHealthCheckPolicyResponse(List extends HealthCheckPolicy> healthcheckPolicies, LoadBalancer lb);
+ LBHealthCheckResponse createLBHealthCheckPolicyResponse(List extends HealthCheckPolicy> healthcheckPolicies,
+ LoadBalancer lb);
LBHealthCheckResponse createLBHealthCheckPolicyResponse(HealthCheckPolicy healthcheckPolicy, LoadBalancer lb);
@@ -319,7 +322,8 @@ public interface ResponseGenerator {
PodResponse createMinimalPodResponse(Pod pod);
- ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon);
+ ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities,
+ Boolean showResourceIcon);
DataCenterGuestIpv6PrefixResponse createDataCenterGuestIpv6PrefixResponse(DataCenterGuestIpv6Prefix prefix);
@@ -361,7 +365,8 @@ public interface ResponseGenerator {
List createTemplateResponses(ResponseView view, long templateId, Long zoneId, boolean readyOnly);
- List createTemplateResponses(ResponseView view, long templateId, Long snapshotId, Long volumeId, boolean readyOnly);
+ List createTemplateResponses(ResponseView view, long templateId, Long snapshotId, Long volumeId,
+ boolean readyOnly);
SecurityGroupResponse createSecurityGroupResponseFromSecurityGroupRule(List extends SecurityRule> securityRules);
@@ -380,14 +385,15 @@ public interface ResponseGenerator {
TemplateResponse createTemplateUpdateResponse(ResponseView view, VirtualMachineTemplate result);
List createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
- Long zoneId, boolean readyOnly);
+ Long zoneId, boolean readyOnly);
List createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
- List zoneIds, boolean readyOnly);
+ List zoneIds, boolean readyOnly);
List createCapacityResponse(List extends Capacity> result, DecimalFormat format);
- TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseView view, List accountNames, Long id);
+ TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseView view, List accountNames,
+ Long id);
AsyncJobResponse queryJobResult(QueryAsyncJobResultCmd cmd);
@@ -401,7 +407,8 @@ public interface ResponseGenerator {
Long getSecurityGroupId(String groupName, long accountId);
- List createIsoResponses(ResponseView view, VirtualMachineTemplate iso, Long zoneId, boolean readyOnly);
+ List createIsoResponses(ResponseView view, VirtualMachineTemplate iso, Long zoneId,
+ boolean readyOnly);
ProjectResponse createProjectResponse(Project project);
@@ -502,13 +509,15 @@ public interface ResponseGenerator {
GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor);
- HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(List> hypervisorGuestOsNames);
+ HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(
+ List> hypervisorGuestOsNames);
SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule sched);
UsageRecordResponse createUsageResponse(Usage usageRecord);
- UsageRecordResponse createUsageResponse(Usage usageRecord, Map> resourceTagResponseMap, boolean oldFormat);
+ UsageRecordResponse createUsageResponse(Usage usageRecord,
+ Map> resourceTagResponseMap, boolean oldFormat);
public Map> getUsageResourceTags();
@@ -520,7 +529,8 @@ public interface ResponseGenerator {
public NicResponse createNicResponse(Nic result);
- ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb, Map lbInstances);
+ ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb,
+ Map lbInstances);
AffinityGroupResponse createAffinityGroupResponse(AffinityGroup group);
@@ -546,9 +556,12 @@ public interface ResponseGenerator {
ManagementServerResponse createManagementResponse(ManagementServerHost mgmt);
- List createHealthCheckResponse(VirtualMachine router, List healthCheckResults);
+ List createHealthCheckResponse(VirtualMachine router,
+ List healthCheckResults);
- RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List hostsUpdated, List hostsSkipped);
+ RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details,
+ List hostsUpdated,
+ List hostsSkipped);
ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon);
@@ -558,11 +571,14 @@ public interface ResponseGenerator {
DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate);
- List createDirectDownloadCertificateHostMapResponse(List hostMappings);
+ List createDirectDownloadCertificateHostMapResponse(
+ List hostMappings);
- DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus status);
+ DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(
+ DirectDownloadManager.HostCertificateStatus status);
- DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair result);
+ DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId,
+ Long hostId, Pair result);
FirewallResponse createIpv6FirewallRuleResponse(FirewallRule acl);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java
new file mode 100644
index 00000000000..358f7f749f6
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java
@@ -0,0 +1,131 @@
+// 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.kms;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.AsyncJobResponse;
+import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
+import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.KMSKey;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(name = "migrateVolumesToKMS",
+ description = "Migrates encrypted volumes to KMS",
+ responseObject = AsyncJobResponse.class,
+ since = "4.23.0",
+ authorized = {RoleType.Admin},
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false)
+public class MigrateVolumesToKMSCmd extends BaseAsyncCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ACCOUNT,
+ type = CommandType.STRING,
+ description = "Migrate volumes for specific account")
+ private String accountName;
+
+ @Parameter(name = ApiConstants.DOMAIN_ID,
+ type = CommandType.UUID,
+ entityType = DomainResponse.class,
+ description = "Domain ID")
+ private Long domainId;
+
+ @Parameter(name = ApiConstants.VOLUME_IDS,
+ type = CommandType.LIST,
+ collectionType = CommandType.UUID,
+ entityType = VolumeResponse.class,
+ required = true,
+ description = "List of volume IDs to migrate")
+ private List volumeIds;
+
+ @Parameter(name = ApiConstants.KMS_KEY_ID,
+ required = true,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "KMS Key ID to use for migrating volumes")
+ private Long kmsKeyId;
+
+ public String getAccountName() {
+ return accountName;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public List getVolumeIds() {
+ return volumeIds;
+ }
+
+ public Long getKmsKeyId() {
+ return kmsKeyId;
+ }
+
+ @Override
+ public void execute() {
+ try {
+ kmsManager.migrateVolumesToKMS(this);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
+ "Failed to migrate volumes to KMS: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ KMSKey key = _entityMgr.findById(KMSKey.class, kmsKeyId);
+ if (key != null) {
+ return key.getAccountId();
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public String getEventType() {
+ return com.cloud.event.EventTypes.EVENT_VOLUME_MIGRATE_TO_KMS;
+ }
+
+ @Override
+ public String getEventDescription() {
+ return "Migrating volumes to KMS key: " + _uuidMgr.getUuid(KMSKey.class, kmsKeyId);
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.KmsKey;
+ }
+
+ @Override
+ public Long getApiResourceId() {
+ return kmsKeyId;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java
index cf4aa41f795..2560d837de1 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java
@@ -27,6 +27,7 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import com.cloud.event.EventTypes;
@@ -51,6 +52,10 @@ public class DetachIsoCmd extends BaseAsyncCmd implements UserCmd {
description = "If true, ejects the ISO before detaching on VMware. Default: false", since = "4.15.1")
protected Boolean forced;
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class,
+ description = "The ID of the ISO to detach. Required when the Instance has more than one ISO attached.", since = "4.23.0")
+ protected Long id;
+
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@@ -104,7 +109,7 @@ public class DetachIsoCmd extends BaseAsyncCmd implements UserCmd {
@Override
public void execute() {
- boolean result = _templateService.detachIso(virtualMachineId, null, isForced());
+ boolean result = _templateService.detachIso(virtualMachineId, id, isForced());
if (result) {
UserVm userVm = _entityMgr.findById(UserVm.class, virtualMachineId);
UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", userVm).get(0);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/CreateKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/CreateKMSKeyCmd.java
new file mode 100644
index 00000000000..c2b8fb6fe09
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/CreateKMSKeyCmd.java
@@ -0,0 +1,159 @@
+/*
+ * 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.user.kms;
+
+import com.cloud.exception.ResourceAllocationException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+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.command.user.UserCmd;
+import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.HSMProfileResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "createKMSKey",
+ description = "Creates a new KMS key (Key Encryption Key) for encryption",
+ responseObject = KMSKeyResponse.class,
+ since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false)
+public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.NAME,
+ required = true,
+ type = CommandType.STRING,
+ description = "Name of the KMS key")
+ private String name;
+
+ @Parameter(name = ApiConstants.DESCRIPTION,
+ type = CommandType.STRING,
+ description = "Description of the KMS key")
+ private String description;
+
+ @Parameter(name = ApiConstants.ZONE_ID,
+ required = true,
+ type = CommandType.UUID,
+ entityType = ZoneResponse.class,
+ description = "Zone ID where the key will be valid")
+ private Long zoneId;
+
+ @Parameter(name = ApiConstants.ACCOUNT,
+ type = CommandType.STRING,
+ description = "Account name (for creating keys for child accounts - requires domain admin or admin)")
+ private String accountName;
+
+ @Parameter(name = ApiConstants.DOMAIN_ID,
+ type = CommandType.UUID,
+ entityType = DomainResponse.class,
+ description = "Domain ID (for creating keys for child accounts - requires domain admin or admin)")
+ private Long domainId;
+
+ @Parameter(name = ApiConstants.PROJECT_ID,
+ type = CommandType.UUID,
+ entityType = ProjectResponse.class,
+ description = "ID of the project to create the KMS key for")
+ private Long projectId;
+
+ @Parameter(name = ApiConstants.KEY_BITS,
+ type = CommandType.INTEGER,
+ description = "Key size in bits: 128, 192, or 256 (default: 256)")
+ private Integer keyBits;
+
+ @Parameter(name = ApiConstants.HSM_PROFILE_ID,
+ type = CommandType.UUID,
+ entityType = HSMProfileResponse.class,
+ required = true,
+ description = "ID of HSM profile to create key in")
+ private Long hsmProfileId;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public String getAccountName() {
+ return accountName;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public Integer getKeyBits() {
+ return keyBits != null ? keyBits : 256;
+ }
+
+ public Long getHsmProfileId() {
+ return hsmProfileId;
+ }
+
+ @Override
+ public void execute() throws ResourceAllocationException {
+ try {
+ KMSKeyResponse response = kmsManager.createKMSKey(this);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
+ "Failed to create KMS key: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true);
+ if (accountId != null) {
+ return accountId;
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.KmsKey;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/DeleteKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/DeleteKMSKeyCmd.java
new file mode 100644
index 00000000000..58f67ec60c4
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/DeleteKMSKeyCmd.java
@@ -0,0 +1,104 @@
+/*
+ * 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.user.kms;
+
+import com.cloud.event.EventTypes;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.KMSKey;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "deleteKMSKey",
+ description = "Deletes a KMS key (only if not in use)",
+ responseObject = SuccessResponse.class,
+ since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false)
+public class DeleteKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID,
+ required = true,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "The UUID of the KMS key to delete")
+ private Long id;
+
+ @Override
+ public void execute() {
+ try {
+ SuccessResponse response = kmsManager.deleteKMSKey(this);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
+ "Failed to delete KMS key: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ KMSKey key = _entityMgr.findById(KMSKey.class, id);
+ if (key != null) {
+ return key.getAccountId();
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.KmsKey;
+ }
+
+ @Override
+ public Long getApiResourceId() {
+ return getId();
+ }
+
+ @Override
+ public String getEventType() {
+ return EventTypes.EVENT_KMS_KEY_DELETE;
+ }
+
+ @Override
+ public String getEventDescription() {
+ return "deleting KMS key: " + getId();
+ }
+
+ public Long getId() {
+ return id;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/ListKMSKeysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/ListKMSKeysCmd.java
new file mode 100644
index 00000000000..c6e4292099f
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/ListKMSKeysCmd.java
@@ -0,0 +1,101 @@
+/*
+ * 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.user.kms;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.HSMProfileResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "listKMSKeys",
+ description = "Lists KMS keys available to the caller",
+ responseObject = KMSKeyResponse.class,
+ responseView = ResponseView.Restricted,
+ since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false)
+public class ListKMSKeysCmd extends BaseListProjectAndAccountResourcesCmd implements UserCmd {
+ private static final String s_name = "listkmskeysresponse";
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "List KMS key by UUID")
+ private Long id;
+
+ @Parameter(name = ApiConstants.ZONE_ID,
+ type = CommandType.UUID,
+ entityType = ZoneResponse.class,
+ description = "Filter by zone ID")
+ private Long zoneId;
+
+ @Parameter(name = ApiConstants.ENABLED,
+ type = CommandType.BOOLEAN,
+ description = "Filter by enabled status")
+ private Boolean enabled;
+
+ @Parameter(name = ApiConstants.HSM_PROFILE_ID,
+ type = CommandType.UUID,
+ entityType = HSMProfileResponse.class,
+ description = "Filter by HSM profile ID")
+ private Long hsmProfileId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public Long getHsmProfileId() {
+ return hsmProfileId;
+ }
+
+ @Override
+ public void execute() {
+ ListResponse listResponse = kmsManager.listKMSKeys(this);
+ listResponse.setResponseName(getCommandName());
+ setResponseObject(listResponse);
+ }
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/RotateKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/RotateKMSKeyCmd.java
new file mode 100644
index 00000000000..e3c6c5822b2
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/RotateKMSKeyCmd.java
@@ -0,0 +1,128 @@
+// 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.user.kms;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.AsyncJobResponse;
+import org.apache.cloudstack.api.response.HSMProfileResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.KMSKey;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "rotateKMSKey",
+ description = "Rotates KMS key (KEK) by creating new version and scheduling gradual re-encryption",
+ responseObject = AsyncJobResponse.class,
+ since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false)
+public class RotateKMSKeyCmd extends BaseAsyncCmd {
+ private static final String s_name = "rotatekmskeyresponse";
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID,
+ required = true,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "KMS Key UUID to rotate")
+ private Long id;
+
+ @Parameter(name = ApiConstants.KEY_BITS,
+ type = CommandType.INTEGER,
+ description = "Key size for new KEK (default: same as current)")
+ private Integer keyBits;
+
+ @Parameter(name = ApiConstants.HSM_PROFILE_ID,
+ type = CommandType.UUID,
+ entityType = HSMProfileResponse.class,
+ description = "The target HSM profile ID for the new KEK version. If provided, migrates the key to "
+ + "this HSM.")
+ private Long hsmProfileId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public Integer getKeyBits() {
+ return keyBits;
+ }
+
+ public Long getHsmProfileId() {
+ return hsmProfileId;
+ }
+
+ @Override
+ public void execute() {
+ try {
+ kmsManager.rotateKMSKey(this);
+ SuccessResponse response = new SuccessResponse();
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
+ "Failed to rotate KMS key: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ KMSKey key = _entityMgr.findById(KMSKey.class, id);
+ if (key != null) {
+ return key.getAccountId();
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public String getEventType() {
+ return com.cloud.event.EventTypes.EVENT_KMS_KEY_ROTATE;
+ }
+
+ @Override
+ public String getEventDescription() {
+ return "Rotating KMS key: " + _uuidMgr.getUuid(KMSKey.class, id);
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.KmsKey;
+ }
+
+ @Override
+ public Long getApiResourceId() {
+ return getId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/UpdateKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/UpdateKMSKeyCmd.java
new file mode 100644
index 00000000000..77654aa13ac
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/UpdateKMSKeyCmd.java
@@ -0,0 +1,113 @@
+/*
+ * 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.user.kms;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+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.KMSKeyResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "updateKMSKey",
+ description = "Updates KMS key name, description, or enabled status",
+ responseObject = KMSKeyResponse.class,
+ since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false)
+public class UpdateKMSKeyCmd extends BaseCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID,
+ required = true,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "The UUID of the KMS key to update")
+ private Long id;
+
+ @Parameter(name = ApiConstants.NAME,
+ type = CommandType.STRING,
+ description = "New name for the key")
+ private String name;
+
+ @Parameter(name = ApiConstants.DESCRIPTION,
+ type = CommandType.STRING,
+ description = "New description for the key")
+ private String description;
+
+ @Parameter(name = ApiConstants.ENABLED,
+ type = CommandType.BOOLEAN,
+ description = "whether the key should be enabled")
+ private Boolean enabled;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void execute() {
+ try {
+ KMSKeyResponse response = kmsManager.updateKMSKey(this);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
+ "Failed to update KMS key: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.KmsKey;
+ }
+
+ @Override
+ public Long getApiResourceId() {
+ return getId();
+ }
+
+ public Long getId() {
+ return id;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/CreateHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/CreateHSMProfileCmd.java
new file mode 100644
index 00000000000..496ddee89b2
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/CreateHSMProfileCmd.java
@@ -0,0 +1,158 @@
+// 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.user.kms.hsm;
+
+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 com.cloud.utils.StringUtils;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+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.DomainResponse;
+import org.apache.cloudstack.api.response.HSMProfileResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.HSMProfile;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+@APICommand(name = "createHSMProfile", description = "Creates a new HSM profile", responseObject = HSMProfileResponse.class,
+ requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0",
+ authorized = { RoleType.Admin })
+public class CreateHSMProfileCmd extends BaseCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true,
+ description = "the name of the HSM profile")
+ private String name;
+
+ @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING,
+ description = "the protocol of the HSM profile (PKCS11, KMIP, etc.). Default is 'pkcs11'")
+ private String protocol;
+
+ @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
+ description = "the zone ID where the HSM profile is available. If null, global scope (for admin only)")
+ private Long zoneId;
+
+ @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
+ description = "the domain ID where the HSM profile is available. If specified without an account, "
+ + "the profile is domain-scoped and usable by all accounts directly in that domain.")
+ private Long domainId;
+
+ @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
+ description = "the account name of the HSM profile owner. Must be used with domainId. "
+ + "Leave empty (with a domainId) to create a domain-scoped profile shared by all "
+ + "accounts in that domain.")
+ private String accountName;
+
+ @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
+ description = "the ID of the project to add the HSM profile for")
+ private Long projectId;
+
+ @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN,
+ description = "whether this is a public HSM profile available to all users globally (root admin only). "
+ + "Default is false")
+ private Boolean isPublic;
+
+ @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "the vendor name of the HSM")
+ private String vendorName;
+
+ @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
+ description = "HSM configuration details (protocol specific)")
+ private Map details;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getProtocol() {
+ if (StringUtils.isBlank(protocol)) {
+ return "pkcs11";
+ }
+ return protocol;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public String getAccountName() {
+ return accountName;
+ }
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public Boolean getIsPublic() {
+ return isPublic != null && isPublic;
+ }
+
+ public String getVendorName() {
+ return vendorName;
+ }
+
+ public Map getDetails() {
+ return convertDetailsToMap(details);
+ }
+
+ @Override
+ public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
+ ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+ try {
+ HSMProfile profile = kmsManager.addHSMProfile(this);
+ HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true);
+ if (accountId != null) {
+ return accountId;
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.HsmProfile;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java
new file mode 100644
index 00000000000..575a4447a69
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java
@@ -0,0 +1,93 @@
+// 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.user.kms.hsm;
+
+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.ApiCommandResourceType;
+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.HSMProfileResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.HSMProfile;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
+ authorized = { RoleType.Admin })
+public class DeleteHSMProfileCmd extends BaseCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true,
+ description = "the ID of the HSM profile")
+ private Long id;
+
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
+ ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+ try {
+ boolean result = kmsManager.deleteHSMProfile(this);
+ if (result) {
+ SuccessResponse response = new SuccessResponse(getCommandName());
+ setResponseObject(response);
+ } else {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete HSM profile");
+ }
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ HSMProfile profile = _entityMgr.findById(HSMProfile.class, id);
+ if (profile != null && profile.getAccountId() > 0) {
+ return profile.getAccountId();
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.HsmProfile;
+ }
+
+ @Override
+ public Long getApiResourceId() {
+ return getId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/ListHSMProfilesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/ListHSMProfilesCmd.java
new file mode 100644
index 00000000000..dc5bb589c42
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/ListHSMProfilesCmd.java
@@ -0,0 +1,85 @@
+// 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.user.kms.hsm;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.HSMProfileResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "listHSMProfiles", description = "Lists HSM profiles", responseObject = HSMProfileResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class ListHSMProfilesCmd extends BaseListProjectAndAccountResourcesCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class,
+ description = "the HSM profile ID")
+ private Long id;
+
+ @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
+ description = "the zone ID")
+ private Long zoneId;
+
+ @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "the protocol of the HSM profile")
+ private String protocol;
+
+ @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "list only enabled profiles")
+ private Boolean enabled;
+
+ @Parameter(name = ApiConstants.IS_PUBLIC,
+ type = CommandType.BOOLEAN,
+ description = "when true, non-admin users see only system (global) profiles")
+ private Boolean isPublic;
+
+ public Long getId() {
+ return id;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public Boolean getIsPublic() {
+ return isPublic;
+ }
+
+ @Override
+ public void execute() {
+ ListResponse response = kmsManager.listHSMProfiles(this);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java
new file mode 100644
index 00000000000..fea7807d616
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java
@@ -0,0 +1,104 @@
+// 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.user.kms.hsm;
+
+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.ApiCommandResourceType;
+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.HSMProfileResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.kms.HSMProfile;
+import org.apache.cloudstack.kms.KMSManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "updateHSMProfile", description = "Updates an HSM profile",
+ responseObject = HSMProfileResponse.class,
+ requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0",
+ authorized = { RoleType.Admin })
+public class UpdateHSMProfileCmd extends BaseCmd {
+
+ @Inject
+ private KMSManager kmsManager;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true,
+ description = "the ID of the HSM profile")
+ private Long id;
+
+ @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the HSM profile")
+ private String name;
+
+ @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN,
+ description = "whether the HSM profile is enabled")
+ private Boolean enabled;
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
+ ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+ try {
+ HSMProfile profile = kmsManager.updateHSMProfile(this);
+ HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ } catch (KMSException e) {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+ }
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ HSMProfile profile = _entityMgr.findById(HSMProfile.class, id);
+ if (profile != null && profile.getAccountId() > 0) {
+ return profile.getAccountId();
+ }
+ return CallContext.current().getCallingAccount().getId();
+ }
+
+ @Override
+ public ApiCommandResourceType getApiResourceType() {
+ return ApiCommandResourceType.HsmProfile;
+ }
+
+ @Override
+ public Long getApiResourceId() {
+ return getId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java
new file mode 100644
index 00000000000..f6268106c31
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java
@@ -0,0 +1,132 @@
+// 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.user.schedule;
+
+import com.cloud.exception.InvalidParameterValueException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
+import org.apache.commons.lang3.EnumUtils;
+
+import javax.inject.Inject;
+import java.util.Date;
+import java.util.Map;
+
+@APICommand(name = "createResourceSchedule", description = "Create Resource Schedule", responseObject = ResourceScheduleResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class CreateResourceScheduleCmd extends BaseCmd {
+
+ @Inject
+ ResourceScheduleManager resourceScheduleManager;
+
+ @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource")
+ private String resourceType;
+
+ @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedule is to be defined")
+ private String resourceId;
+
+ @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule")
+ private String description;
+
+ @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "Schedule for action on resource in cron format. e.g. '0 15 10 * *' for 'at 15:00 on 10th day of every month'")
+ private String schedule;
+
+ @Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = true, description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.")
+ private String timeZone;
+
+ @Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = true, description = "Action to take on the resource.")
+ private String action;
+
+ @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute. (Format \"yyyy-MM-dd hh:mm:ss\")")
+ private Date startDate;
+
+ @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive. (Format \"yyyy-MM-dd hh:mm:ss\")")
+ private Date endDate;
+
+ @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable schedule. Defaults to true")
+ private Boolean enabled;
+
+ @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.")
+ private Map details;
+
+ public ApiCommandResourceType getResourceType() {
+ ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
+ if (type == null) {
+ throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
+ }
+ return type;
+ }
+
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getSchedule() {
+ return schedule;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ public Boolean getEnabled() {
+ if (enabled == null) {
+ enabled = true;
+ }
+ return enabled;
+ }
+
+ public Map getDetails() {
+ return convertDetailsToMap(details);
+ }
+
+ @Override
+ public void execute() {
+ ResourceScheduleResponse response = resourceScheduleManager.createSchedule(getResourceType(), getResourceId(),
+ getDescription(), getSchedule(), getTimeZone(), getAction(), getStartDate(), getEndDate(), getEnabled(), getDetails());
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccount().getAccountId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java
new file mode 100644
index 00000000000..fe9a695df63
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java
@@ -0,0 +1,86 @@
+// 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.user.schedule;
+
+import com.cloud.exception.InvalidParameterValueException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
+import org.apache.commons.lang3.EnumUtils;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(name = "deleteResourceSchedule", description = "Delete Resource Schedule", responseObject = SuccessResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class DeleteResourceScheduleCmd extends BaseCmd {
+
+ @Inject
+ ResourceScheduleManager resourceScheduleManager;
+
+ @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource")
+ private String resourceType;
+
+ @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be deleted")
+ private String resourceId;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule to be deleted")
+ private Long id;
+
+ @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids to be deleted")
+ private List ids;
+
+ public ApiCommandResourceType getResourceType() {
+ ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
+ if (type == null) {
+ throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
+ }
+ return type;
+ }
+
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public List getIds() {
+ return ids;
+ }
+
+ @Override
+ public void execute() {
+ resourceScheduleManager.removeSchedule(getResourceType(), getResourceId(), getId(), getIds());
+ SuccessResponse response = new SuccessResponse(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccount().getAccountId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java
new file mode 100644
index 00000000000..1c8869eff9c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java
@@ -0,0 +1,97 @@
+// 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.user.schedule;
+
+import com.cloud.exception.InvalidParameterValueException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
+import org.apache.commons.lang3.EnumUtils;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(name = "listResourceSchedule", description = "List Resource Schedules", responseObject = ResourceScheduleResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class ListResourceScheduleCmd extends BaseListCmd {
+
+ @Inject
+ ResourceScheduleManager resourceScheduleManager;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule")
+ private Long id;
+
+ @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids")
+ private List ids;
+
+ @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource.")
+ private String resourceType;
+
+ @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be listed.")
+ private String resourceId;
+
+ @Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = false, description = "Action to take on the resource.")
+ private String action;
+
+ @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Filter by enabled status.")
+ private Boolean enabled;
+
+ public Long getId() {
+ return id;
+ }
+
+ public List getIds() {
+ return ids;
+ }
+
+ public ApiCommandResourceType getResourceType() {
+ ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
+ if (type == null) {
+ throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
+ }
+ return type;
+ }
+
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void execute() {
+ ListResponse response = resourceScheduleManager.listSchedule(
+ getId(), getIds(), getResourceType(), getResourceId(), getAction(), getEnabled(),
+ getStartIndex(), getPageSizeVal()
+ );
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java
new file mode 100644
index 00000000000..5422588853e
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java
@@ -0,0 +1,108 @@
+// 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.user.schedule;
+
+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.response.ResourceScheduleResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
+
+import javax.inject.Inject;
+import java.util.Date;
+import java.util.Map;
+
+@APICommand(name = "updateResourceSchedule", description = "Update Resource Schedule", responseObject = ResourceScheduleResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class UpdateResourceScheduleCmd extends BaseCmd {
+
+ @Inject
+ ResourceScheduleManager resourceScheduleManager;
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = true, description = "ID of the schedule to be updated")
+ private Long id;
+
+ @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule")
+ private String description;
+
+ @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = false, description = "Schedule for action on resource in cron format.")
+ private String schedule;
+
+ @Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = false, description = "Specifies a timezone for this command.")
+ private String timeZone;
+
+ @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active.")
+ private Date startDate;
+
+ @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive.")
+ private Date endDate;
+
+ @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable or disable the schedule.")
+ private Boolean enabled;
+
+ @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.")
+ private Map details;
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getSchedule() {
+ return schedule;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public Map getDetails() {
+ return convertDetailsToMap(details);
+ }
+
+ @Override
+ public void execute() {
+ ResourceScheduleResponse response = resourceScheduleManager.updateSchedule(getId(), getDescription(), getSchedule(),
+ getTimeZone(), getStartDate(), getEndDate(), getEnabled(), getDetails());
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccount().getAccountId();
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
index 68b0821ba44..38e9921853a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
@@ -40,12 +40,14 @@ import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
import org.apache.cloudstack.api.response.NetworkResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.UserDataResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.kms.KMSKey;
import org.apache.cloudstack.vm.lease.VMLeaseManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
@@ -126,11 +128,19 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
since = "4.4")
private Long rootdisksize;
+ @ACL
+ @Parameter(name = ApiConstants.ROOT_DISK_KMS_KEY_ID,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "ID of the KMS Key to use for root disk encryption",
+ since = "4.23.0")
+ private Long rootDiskKmsKeyId;
+
@Parameter(name = ApiConstants.DATADISKS_DETAILS,
type = CommandType.MAP,
since = "4.21.0",
description = "Disk offering details for creating multiple data volumes. Mutually exclusive with diskOfferingId." +
- " Example: datadisksdetails[0].diskofferingid=a2a73a84-19db-4852-8930-dfddef053341&datadisksdetails[0].size=10&datadisksdetails[0].miniops=100&datadisksdetails[0].maxiops=200")
+ " Example: datadisksdetails[0].diskofferingid=a2a73a84-19db-4852-8930-dfddef053341&datadisksdetails[0].size=10&datadisksdetails[0].miniops=100&datadisksdetails[0].maxiops=200&datadisksdetails[0].kmskeyid=")
private Map dataDisksDetails;
@Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "an optional group for the virtual machine")
@@ -300,6 +310,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
return diskOfferingId;
}
+ public Long getRootDiskKmsKeyId() {
+ return rootDiskKmsKeyId;
+ }
+
public String getDeploymentPlanner() {
return deploymentPlanner;
}
@@ -581,7 +595,19 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
minIops = Long.parseLong(dataDisk.get(ApiConstants.MIN_IOPS));
maxIops = Long.parseLong(dataDisk.get(ApiConstants.MAX_IOPS));
}
- VmDiskInfo vmDiskInfo = new VmDiskInfo(diskOffering, size, minIops, maxIops, deviceId);
+
+ // Extract KMS key ID if provided
+ Long kmsKeyId = null;
+ String kmsKeyUuid = dataDisk.get(ApiConstants.KMS_KEY_ID);
+ if (kmsKeyUuid != null) {
+ KMSKey kmsKey = _entityMgr.findByUuid(org.apache.cloudstack.kms.KMSKey.class, kmsKeyUuid);
+ if (kmsKey == null) {
+ throw new InvalidParameterValueException("Unable to find KMS key " + kmsKeyUuid);
+ }
+ kmsKeyId = kmsKey.getId();
+ }
+
+ VmDiskInfo vmDiskInfo = new VmDiskInfo(diskOffering, size, minIops, maxIops, deviceId, kmsKeyId);
vmDiskInfoList.add(vmDiskInfo);
}
this.dataDiskInfoList = vmDiskInfoList;
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java
index 7e9bdd942ed..1fce8caf8e1 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java
@@ -22,23 +22,26 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Date;
+@Deprecated
@APICommand(name = "createVMSchedule", description = "Create Instance Schedule", responseObject = VMScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateVMScheduleCmd extends BaseCmd {
@Inject
- VMScheduleManager vmScheduleManager;
+ ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
@@ -75,14 +78,14 @@ public class CreateVMScheduleCmd extends BaseCmd {
type = CommandType.DATE,
required = false,
description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute."
- + "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ + "(Format \"yyyy-MM-dd hh:mm:ss\")")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE,
type = CommandType.DATE,
required = false,
description = "End date after which the schedule becomes inactive"
- + "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ + "(Format \"yyyy-MM-dd hh:mm:ss\")")
private Date endDate;
@Parameter(name = ApiConstants.ENABLED,
@@ -91,9 +94,9 @@ public class CreateVMScheduleCmd extends BaseCmd {
description = "Enable Instance schedule. Defaults to true")
private Boolean enabled;
- /////////////////////////////////////////////////////
- /////////////////// Accessors ///////////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////////// Accessors ///////////////////////
+ /// //////////////////////////////////////////////////
public Long getVmId() {
return vmId;
@@ -130,13 +133,19 @@ public class CreateVMScheduleCmd extends BaseCmd {
return enabled;
}
- /////////////////////////////////////////////////////
- /////////////// API Implementation///////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////// API Implementation///////////////////
+ /// //////////////////////////////////////////////////
@Override
public void execute() {
- VMScheduleResponse response = vmScheduleManager.createSchedule(this);
+ String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
+
+ ResourceScheduleResponse scheduleResponse = resourceScheduleManager.createSchedule(
+ ApiCommandResourceType.VirtualMachine,
+ resourceIdStr, getDescription(), getSchedule(), getTimeZone(), getAction(),
+ getStartDate(), getEndDate(), getEnabled(), null);
+ VMScheduleResponse response = new VMScheduleResponse(scheduleResponse);
response.setResponseName(getCommandName());
setResponseObject(response);
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java
index f34d07b045d..f40db4ee048 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java
@@ -22,6 +22,7 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
@@ -30,19 +31,19 @@ import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMSchedule;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Collections;
import java.util.List;
+@Deprecated
@APICommand(name = "deleteVMSchedule", description = "Delete Instance Schedule.", responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DeleteVMScheduleCmd extends BaseCmd {
@Inject
- VMScheduleManager vmScheduleManager;
+ ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
@@ -50,12 +51,14 @@ public class DeleteVMScheduleCmd extends BaseCmd {
required = true,
description = "ID of Instance")
private Long vmId;
+
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = VMScheduleResponse.class,
required = false,
description = "ID of Instance schedule")
private Long id;
+
@Parameter(name = ApiConstants.IDS,
type = CommandType.LIST,
collectionType = CommandType.UUID,
@@ -64,9 +67,9 @@ public class DeleteVMScheduleCmd extends BaseCmd {
description = "IDs of Instance schedule")
private List ids;
- /////////////////////////////////////////////////////
- /////////////////// Accessors ///////////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////////// Accessors ///////////////////////
+ /// //////////////////////////////////////////////////
public Long getId() {
return id;
@@ -83,18 +86,21 @@ public class DeleteVMScheduleCmd extends BaseCmd {
return vmId;
}
- /////////////////////////////////////////////////////
- /////////////// API Implementation///////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////// API Implementation///////////////////
+ /// //////////////////////////////////////////////////
@Override
public void execute() {
- long rowsRemoved = vmScheduleManager.removeSchedule(this);
+ String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
+ long rowsRemoved = resourceScheduleManager.removeSchedule(
+ ApiCommandResourceType.VirtualMachine,
+ resourceIdStr, getId(), getIds());
if (rowsRemoved > 0) {
final SuccessResponse response = new SuccessResponse();
response.setResponseName(getCommandName());
- response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+ response.setObjectName("vmschedule");
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete Instance Schedules");
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java
index be94315abe7..c7a9a75f2ea 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java
@@ -20,23 +20,27 @@ package org.apache.cloudstack.api.command.user.vm;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMSchedule;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+@Deprecated
@APICommand(name = "listVMSchedule", description = "List Instance Schedules.", responseObject = VMScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListVMScheduleCmd extends BaseListCmd {
@Inject
- VMScheduleManager vmScheduleManager;
+ ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
@@ -61,12 +65,12 @@ public class ListVMScheduleCmd extends BaseListCmd {
@Parameter(name = ApiConstants.ENABLED,
type = CommandType.BOOLEAN,
required = false,
- description = "ID of Instance schedule")
+ description = "Filter by enabled status")
private Boolean enabled;
- /////////////////////////////////////////////////////
- /////////////////// Accessors ///////////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////////// Accessors ///////////////////////
+ /// //////////////////////////////////////////////////
public Long getVmId() {
return vmId;
@@ -84,14 +88,26 @@ public class ListVMScheduleCmd extends BaseListCmd {
return enabled;
}
- /////////////////////////////////////////////////////
- /////////////// API Implementation///////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////// API Implementation///////////////////
+ /// //////////////////////////////////////////////////
@Override
public void execute() {
- ListResponse response = vmScheduleManager.listSchedule(this);
+ String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
+
+ ListResponse scheduleResponse = resourceScheduleManager.listSchedule(
+ getId(), null, ApiCommandResourceType.VirtualMachine, resourceIdStr, getAction(), getEnabled(),
+ getStartIndex(), getPageSizeVal()
+ );
+
+ List vmScheduleResponses = new ArrayList<>();
+ for (ResourceScheduleResponse resourceScheduleResponse : scheduleResponse.getResponses()) {
+ vmScheduleResponses.add(new VMScheduleResponse(resourceScheduleResponse));
+ }
+ ListResponse response = new ListResponse<>();
+ response.setResponses(vmScheduleResponses, scheduleResponse.getCount());
response.setResponseName(getCommandName());
- response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+ response.setObjectName("vmschedule");
setResponseObject(response);
}
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java
index b7222944fe0..9b5b0537310 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java
@@ -22,22 +22,25 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMSchedule;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceSchedule;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Date;
+@Deprecated
@APICommand(name = "updateVMSchedule", description = "Update Instance Schedule.", responseObject = VMScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class UpdateVMScheduleCmd extends BaseCmd {
@Inject
- VMScheduleManager vmScheduleManager;
+ ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
@@ -68,14 +71,14 @@ public class UpdateVMScheduleCmd extends BaseCmd {
type = CommandType.DATE,
required = false,
description = "Start date from which the schedule becomes active"
- + "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ + "(Format \"yyyy-MM-dd hh:mm:ss\")")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE,
type = CommandType.DATE,
required = false,
description = "End date after which the schedule becomes inactive"
- + "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ + "(Format \"yyyy-MM-dd hh:mm:ss\")")
private Date endDate;
@Parameter(name = ApiConstants.ENABLED,
@@ -84,9 +87,9 @@ public class UpdateVMScheduleCmd extends BaseCmd {
description = "Enable Instance schedule")
private Boolean enabled;
- /////////////////////////////////////////////////////
- /////////////////// Accessors ///////////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////////// Accessors ///////////////////////
+ /// //////////////////////////////////////////////////
public Long getId() {
return id;
@@ -116,24 +119,29 @@ public class UpdateVMScheduleCmd extends BaseCmd {
return enabled;
}
- /////////////////////////////////////////////////////
- /////////////// API Implementation///////////////////
- /////////////////////////////////////////////////////
+ /// //////////////////////////////////////////////////
+ /// //////////// API Implementation///////////////////
+ /// //////////////////////////////////////////////////
@Override
public void execute() {
- VMScheduleResponse response = vmScheduleManager.updateSchedule(this);
+ ResourceScheduleResponse scheduleResponse = resourceScheduleManager.updateSchedule(
+ getId(), getDescription(), getSchedule(), getTimeZone(), getStartDate(), getEndDate(), getEnabled(), null);
+ VMScheduleResponse response = new VMScheduleResponse(scheduleResponse);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
- VMSchedule vmSchedule = _entityMgr.findById(VMSchedule.class, getId());
- if (vmSchedule == null) {
- throw new InvalidParameterValueException(String.format("Unable to find vmSchedule by id=%d", getId()));
+ ResourceSchedule schedule = _entityMgr.findById(ResourceSchedule.class, getId());
+ if (schedule == null || !ApiCommandResourceType.VirtualMachine.equals(schedule.getResourceType())) {
+ throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId()));
+ }
+ VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, schedule.getResourceId());
+ if (vm == null) {
+ throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId()));
}
- VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmSchedule.getVmId());
return vm.getAccountId();
}
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java
index ec7a626fa15..538e263ae9d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java
@@ -30,6 +30,7 @@ import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
@@ -110,6 +111,13 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
description = "The ID of the Instance; to be used with snapshot Id, Instance to which the volume gets attached after creation")
private Long virtualMachineId;
+ @Parameter(name = ApiConstants.KMS_KEY_ID,
+ type = CommandType.UUID,
+ entityType = KMSKeyResponse.class,
+ description = "ID of the KMS Key for volume encryption (required if encryption enabled for zone)",
+ since = "4.23.0")
+ private Long kmsKeyId;
+
@Parameter(name = ApiConstants.STORAGE_ID,
type = CommandType.UUID,
entityType = StoragePoolResponse.class,
@@ -190,6 +198,10 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
return virtualMachineId;
}
+ public Long getKmsKeyId() {
+ return kmsKeyId;
+ }
+
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java
index a4cd299dae9..88f87e941e7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java
@@ -29,6 +29,7 @@ import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.PodResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
@@ -90,6 +91,9 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
@Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "List volumes by disk offering", since = "4.4")
private Long diskOfferingId;
+ @Parameter(name = ApiConstants.KMS_KEY_ID, type = CommandType.UUID, entityType = KMSKeyResponse.class, description = "List volumes by KMS Key", since = "4.23")
+ private Long kmsKeyId;
+
@Parameter(name = ApiConstants.DISPLAY_VOLUME, type = CommandType.BOOLEAN, description = "List resources by display flag; only ROOT admin is eligible to pass this parameter", since = "4.4", authorized = {
RoleType.Admin})
private Boolean display;
@@ -136,6 +140,10 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
return diskOfferingId;
}
+ public Long getKmsKeyId() {
+ return kmsKeyId;
+ }
+
public String getType() {
return type;
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AttachedIsoResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AttachedIsoResponse.java
new file mode 100644
index 00000000000..b259de56218
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/AttachedIsoResponse.java
@@ -0,0 +1,76 @@
+// 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 org.apache.cloudstack.api.BaseResponse;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+
+public class AttachedIsoResponse extends BaseResponse {
+
+ @SerializedName("id")
+ @Param(description = "The ID of the attached ISO")
+ private String id;
+
+ @SerializedName("name")
+ @Param(description = "The name of the attached ISO")
+ private String name;
+
+ @SerializedName("displaytext")
+ @Param(description = "The display text of the attached ISO")
+ private String displayText;
+
+ @SerializedName("deviceseq")
+ @Param(description = "The cdrom slot that holds this ISO (3=hdc, 4=hdd, ...)")
+ private Integer deviceSeq;
+
+ @SerializedName("bootable")
+ @Param(description = "Whether this is the bootable ISO for the VM")
+ private Boolean bootable;
+
+ public AttachedIsoResponse() {
+ }
+
+ public AttachedIsoResponse(String id, String name, String displayText, Integer deviceSeq, boolean bootable) {
+ this.id = id;
+ this.name = name;
+ this.displayText = displayText;
+ this.deviceSeq = deviceSeq;
+ this.bootable = bootable;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDisplayText() {
+ return displayText;
+ }
+
+ public Integer getDeviceSeq() {
+ return deviceSeq;
+ }
+
+ public Boolean getBootable() {
+ return bootable;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HSMProfileResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HSMProfileResponse.java
new file mode 100644
index 00000000000..7576c76bef1
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/HSMProfileResponse.java
@@ -0,0 +1,254 @@
+// 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 java.util.Date;
+import java.util.Map;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.kms.HSMProfile;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+
+@EntityReference(value = HSMProfile.class)
+public class HSMProfileResponse extends BaseResponse implements ControlledViewEntityResponse {
+ @SerializedName(ApiConstants.ID)
+ @Param(description = "the ID of the HSM profile")
+ private String id;
+
+ @SerializedName(ApiConstants.NAME)
+ @Param(description = "the name of the HSM profile")
+ private String name;
+
+ @SerializedName(ApiConstants.PROTOCOL)
+ @Param(description = "the protocol of the HSM profile")
+ private String protocol;
+
+ @SerializedName(ApiConstants.ACCOUNT_ID)
+ @Param(description = "the account ID of the HSM profile owner")
+ private String accountId;
+
+ @SerializedName(ApiConstants.ACCOUNT)
+ @Param(description = "the account name of the HSM profile owner")
+ private String accountName;
+
+ @SerializedName(ApiConstants.DOMAIN_ID)
+ @Param(description = "the domain ID of the HSM profile owner")
+ private String domainId;
+
+ @SerializedName(ApiConstants.DOMAIN)
+ @Param(description = "the domain name of the HSM profile owner")
+ private String domainName;
+
+ @SerializedName(ApiConstants.DOMAIN_PATH)
+ @Param(description = "the domain path of the HSM profile owner")
+ private String domainPath;
+
+ @SerializedName(ApiConstants.PROJECT_ID)
+ @Param(description = "the project ID of the HSM profile owner")
+ private String projectId;
+
+ @SerializedName(ApiConstants.PROJECT)
+ @Param(description = "the project name of the HSM profile owner")
+ private String projectName;
+
+ @SerializedName(ApiConstants.ZONE_ID)
+ @Param(description = "the zone ID where the HSM profile is available")
+ private String zoneId;
+
+ @SerializedName(ApiConstants.ZONE_NAME)
+ @Param(description = "the zone name where the HSM profile is available")
+ private String zoneName;
+
+ @SerializedName("vendor")
+ @Param(description = "the vendor name of the HSM profile")
+ private String vendorName;
+
+ @SerializedName(ApiConstants.STATE)
+ @Param(description = "the state of the HSM profile")
+ private String state;
+
+ @SerializedName(ApiConstants.ENABLED)
+ @Param(description = "whether the HSM profile is enabled")
+ private Boolean enabled;
+
+ @SerializedName(ApiConstants.IS_PUBLIC)
+ @Param(description = "whether this is a system HSM profile available to all users globally")
+ private Boolean isPublic;
+
+ @SerializedName(ApiConstants.CREATED)
+ @Param(description = "the date the HSM profile was created")
+ private Date created;
+
+ @SerializedName(ApiConstants.DETAILS)
+ @Param(description = "HSM configuration details (sensitive values are encrypted)")
+ private Map details;
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public void setAccountId(String accountId) {
+ this.accountId = accountId;
+ }
+
+ @Override
+ public void setAccountName(String accountName) {
+ this.accountName = accountName;
+ }
+
+ @Override
+ public void setDomainId(String domainId) {
+ this.domainId = domainId;
+ }
+
+ @Override
+ public void setDomainName(String domainName) {
+ this.domainName = domainName;
+ }
+
+ @Override
+ public void setDomainPath(String domainPath) {
+ this.domainPath = domainPath;
+ }
+
+ @Override
+ public void setProjectId(String projectId) {
+ this.projectId = projectId;
+ }
+
+ @Override
+ public void setProjectName(String projectName) {
+ this.projectName = projectName;
+ }
+
+ public void setZoneId(String zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public void setZoneName(String zoneName) {
+ this.zoneName = zoneName;
+ }
+
+ public void setVendorName(String vendorName) {
+ this.vendorName = vendorName;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setIsPublic(Boolean isPublic) {
+ this.isPublic = isPublic;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public void setDetails(Map details) {
+ this.details = details;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public String getAccountName() {
+ return accountName;
+ }
+
+ public String getDomainId() {
+ return domainId;
+ }
+
+ public String getDomainName() {
+ return domainName;
+ }
+
+ public String getDomainPath() {
+ return domainPath;
+ }
+
+ public String getProjectId() {
+ return projectId;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public String getZoneId() {
+ return zoneId;
+ }
+
+ public String getZoneName() {
+ return zoneName;
+ }
+
+ public String getVendorName() {
+ return vendorName;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public Boolean getPublic() {
+ return isPublic;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public Map getDetails() {
+ return details;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/KMSKeyResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/KMSKeyResponse.java
new file mode 100644
index 00000000000..cd7a360a497
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/KMSKeyResponse.java
@@ -0,0 +1,272 @@
+/*
+ * 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.acl.RoleType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.kms.KMSKey;
+
+import java.util.Date;
+
+@EntityReference(value = KMSKey.class)
+public class KMSKeyResponse extends BaseResponse implements ControlledViewEntityResponse {
+
+ @SerializedName(ApiConstants.ID)
+ @Param(description = "the UUID of the key")
+ private String id;
+
+ @SerializedName(ApiConstants.NAME)
+ @Param(description = "the name of the key")
+ private String name;
+
+ @SerializedName(ApiConstants.DESCRIPTION)
+ @Param(description = "the description of the key")
+ private String description;
+
+ @SerializedName(ApiConstants.ACCOUNT)
+ @Param(description = "the account owning the key")
+ private String accountName;
+
+ @SerializedName(ApiConstants.ACCOUNT_ID)
+ @Param(description = "the account ID owning the key")
+ private String accountId;
+
+ @SerializedName(ApiConstants.DOMAIN_ID)
+ @Param(description = "the domain ID of the key")
+ private String domainId;
+
+ @SerializedName(ApiConstants.DOMAIN)
+ @Param(description = "the domain name of the key")
+ private String domainName;
+
+ @SerializedName(ApiConstants.DOMAIN_PATH)
+ @Param(description = "the domain path of the key")
+ private String domainPath;
+
+ @SerializedName(ApiConstants.ZONE_ID)
+ @Param(description = "the zone ID where the key is valid")
+ private String zoneId;
+
+ @SerializedName(ApiConstants.ZONE_NAME)
+ @Param(description = "the zone name where the key is valid")
+ private String zoneName;
+
+ @SerializedName(ApiConstants.HSM_PROFILE_ID)
+ @Param(description = "the zone ID where the key is valid")
+ private String hsmProfileId;
+
+ @SerializedName(ApiConstants.HSM_PROFILE)
+ @Param(description = "the zone name where the key is valid")
+ private String hsmProfileName;
+
+ @SerializedName(ApiConstants.ALGORITHM)
+ @Param(description = "the encryption algorithm")
+ private String algorithm;
+
+ @SerializedName(ApiConstants.KEY_BITS)
+ @Param(description = "the key size in bits")
+ private Integer keyBits;
+
+ @SerializedName(ApiConstants.VERSION)
+ @Param(description = "the key size in bits")
+ private Integer version;
+
+ @SerializedName(ApiConstants.ENABLED)
+ @Param(description = "whether the key is enabled")
+ private Boolean enabled;
+
+ @SerializedName(ApiConstants.CREATED)
+ @Param(description = "the creation timestamp")
+ private Date created;
+
+ @SerializedName(ApiConstants.PROJECT_ID)
+ @Param(description = "the project ID of the key")
+ private String projectId;
+
+ @SerializedName(ApiConstants.PROJECT)
+ @Param(description = "the project name of the key")
+ private String projectName;
+
+ @SerializedName(ApiConstants.KEK_LABEL)
+ @Param(description = "the provider-specific KEK label (admin only)", authorized = { RoleType.Admin })
+ private String kekLabel;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getAccountName() {
+ return accountName;
+ }
+
+ @Override
+ public void setAccountName(String accountName) {
+ this.accountName = accountName;
+ }
+
+ @Override
+ public void setProjectId(String projectId) {
+ this.projectId = projectId;
+ }
+
+ @Override
+ public void setProjectName(String projectName) {
+ this.projectName = projectName;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(String accountId) {
+ this.accountId = accountId;
+ }
+
+ public String getDomainId() {
+ return domainId;
+ }
+
+ @Override
+ public void setDomainId(String domainId) {
+ this.domainId = domainId;
+ }
+
+ public String getDomainName() {
+ return domainName;
+ }
+
+ @Override
+ public void setDomainName(String domainName) {
+ this.domainName = domainName;
+ }
+
+ public String getDomainPath() {
+ return domainPath;
+ }
+
+ @Override
+ public void setDomainPath(String domainPath) {
+ this.domainPath = domainPath;
+ }
+
+ public String getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(String zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public String getZoneName() {
+ return zoneName;
+ }
+
+ public void setZoneName(String zoneName) {
+ this.zoneName = zoneName;
+ }
+
+ public String getHsmProfileId() {
+ return hsmProfileId;
+ }
+
+ public void setHsmProfileId(String hsmProfileId) {
+ this.hsmProfileId = hsmProfileId;
+ }
+
+ public String getHsmProfileName() {
+ return hsmProfileName;
+ }
+
+ public void setHsmProfileName(String hsmProfileName) {
+ this.hsmProfileName = hsmProfileName;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public Integer getKeyBits() {
+ return keyBits;
+ }
+
+ public void setKeyBits(Integer keyBits) {
+ this.keyBits = keyBits;
+ }
+
+ public Integer getVersion() {
+ return version;
+ }
+
+ public void setVersion(Integer version) {
+ this.version = version;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public String getKekLabel() {
+ return kekLabel;
+ }
+
+ public void setKekLabel(String kekLabel) {
+ this.kekLabel = kekLabel;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java
new file mode 100644
index 00000000000..ff799b467cf
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java
@@ -0,0 +1,178 @@
+/*
+ * 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 java.util.Date;
+import java.util.Map;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.schedule.ResourceSchedule;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+
+@EntityReference(value = ResourceSchedule.class)
+public class ResourceScheduleResponse extends BaseResponse {
+ @SerializedName(ApiConstants.ID)
+ @Param(description = "The ID of resource schedule")
+ private String id;
+
+ @SerializedName(ApiConstants.RESOURCE_TYPE)
+ @Param(description = "Type of the resource")
+ private ApiCommandResourceType resourceType;
+
+ @SerializedName(ApiConstants.RESOURCE_ID)
+ @Param(description = "ID of the resource")
+ private String resourceId;
+
+ @SerializedName(ApiConstants.DESCRIPTION)
+ @Param(description = "Description of resource schedule")
+ private String description;
+
+ @SerializedName(ApiConstants.SCHEDULE)
+ @Param(description = "Cron formatted resource schedule")
+ private String schedule;
+
+ @SerializedName(ApiConstants.TIMEZONE)
+ @Param(description = "Timezone of the schedule")
+ private String timeZone;
+
+ @SerializedName(ApiConstants.ACTION)
+ @Param(description = "Action")
+ private ResourceSchedule.Action action;
+
+ @SerializedName(ApiConstants.ENABLED)
+ @Param(description = "Resource schedule is enabled")
+ private boolean enabled;
+
+ @SerializedName(ApiConstants.START_DATE)
+ @Param(description = "Date from which the schedule is active")
+ private Date startDate;
+
+ @SerializedName(ApiConstants.END_DATE)
+ @Param(description = "Date after which the schedule becomes inactive")
+ private Date endDate;
+
+ @SerializedName(ApiConstants.DETAILS)
+ @Param(description = "Schedule details")
+ private Map details;
+
+ @SerializedName(ApiConstants.CREATED)
+ @Param(description = "Date when the schedule was created")
+ private Date created;
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setResourceType(ApiCommandResourceType resourceType) {
+ this.resourceType = resourceType;
+ }
+
+ public ApiCommandResourceType getResourceType() {
+ return resourceType;
+ }
+
+ public void setResourceId(String resourceId) {
+ this.resourceId = resourceId;
+ }
+
+ public String getResourceId() {
+ return resourceId;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setSchedule(String schedule) {
+ this.schedule = schedule;
+ }
+
+ public String getSchedule() {
+ return schedule;
+ }
+
+ public void setTimeZone(String timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public void setAction(ResourceSchedule.Action action) {
+ this.action = action;
+ }
+
+ public ResourceSchedule.Action getAction() {
+ return action;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean getEnabled() {
+ return enabled;
+ }
+
+ public void setStartDate(Date startDate) {
+ this.startDate = startDate;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ public void setEndDate(Date endDate) {
+ this.endDate = endDate;
+ }
+
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ public void setDetails(Map details) {
+ this.details = details;
+ }
+
+ public Map getDetails() {
+ return details;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
index a7f6dff96f8..4d6eae2fad2 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
@@ -166,6 +166,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "An alternate display text of the ISO attached to the Instance")
private String isoDisplayText;
+ @SerializedName("isos")
+ @Param(description = "All ISOs attached to the Instance, keyed by cdrom slot. The first entry mirrors isoid/isoname for back-compat.", responseObject = AttachedIsoResponse.class, since = "4.23.0")
+ private List isos;
+
+ @SerializedName("isomaxcount")
+ @Param(description = "Maximum number of ISOs that may be attached to this Instance, after applying the cluster-scoped vm.iso.max.count and the hypervisor's own cap.", since = "4.23.0")
+ private Integer isoMaxCount;
+
@SerializedName(ApiConstants.SERVICE_OFFERING_ID)
@Param(description = "The ID of the service offering of the Instance")
private String serviceOfferingId;
@@ -871,6 +879,22 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
this.isoId = isoId;
}
+ public void setIsos(List isos) {
+ this.isos = isos;
+ }
+
+ public List getIsos() {
+ return isos;
+ }
+
+ public void setIsoMaxCount(Integer isoMaxCount) {
+ this.isoMaxCount = isoMaxCount;
+ }
+
+ public Integer getIsoMaxCount() {
+ return isoMaxCount;
+ }
+
public void setIsoName(String isoName) {
this.isoName = isoName;
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java
index 6800c25b023..34455b3a0bf 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java
@@ -23,11 +23,11 @@ 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.vm.schedule.VMSchedule;
+import org.apache.cloudstack.schedule.ResourceSchedule;
import java.util.Date;
-@EntityReference(value = VMSchedule.class)
+@EntityReference(value = ResourceSchedule.class)
public class VMScheduleResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "The ID of Instance schedule")
@@ -51,7 +51,7 @@ public class VMScheduleResponse extends BaseResponse {
@SerializedName(ApiConstants.ACTION)
@Param(description = "Action")
- private VMSchedule.Action action;
+ private ResourceSchedule.Action action;
@SerializedName(ApiConstants.ENABLED)
@Param(description = "VM schedule is enabled")
@@ -69,6 +69,20 @@ public class VMScheduleResponse extends BaseResponse {
@Param(description = "Date when the schedule was created")
private Date created;
+ public VMScheduleResponse(ResourceScheduleResponse scheduleResponse) {
+ super("vmschedule");
+ this.id = scheduleResponse.getId();
+ this.vmId = scheduleResponse.getResourceId();
+ this.description = scheduleResponse.getDescription();
+ this.schedule = scheduleResponse.getSchedule();
+ this.timeZone = scheduleResponse.getTimeZone();
+ this.action = scheduleResponse.getAction();
+ this.enabled = scheduleResponse.getEnabled();
+ this.startDate = scheduleResponse.getStartDate();
+ this.endDate = scheduleResponse.getEndDate();
+ this.created = scheduleResponse.getCreated();
+ }
+
public void setId(String id) {
this.id = id;
}
@@ -89,7 +103,7 @@ public class VMScheduleResponse extends BaseResponse {
this.timeZone = timeZone;
}
- public void setAction(VMSchedule.Action action) {
+ public void setAction(ResourceSchedule.Action action) {
this.action = action;
}
@@ -105,5 +119,7 @@ public class VMScheduleResponse extends BaseResponse {
this.endDate = endDate;
}
- public void setCreated(Date created) {this.created = created;}
+ public void setCreated(Date created) {
+ this.created = created;
+ }
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java
index 058ea50f991..031d9315cd0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java
@@ -309,6 +309,18 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "the format of the disk encryption if applicable", since = "4.19.1")
private String encryptionFormat;
+ @SerializedName(ApiConstants.KMS_KEY)
+ @Param(description = "KMS key name of the volume", since = "4.23.0")
+ private String kmsKey;
+
+ @SerializedName(ApiConstants.KMS_KEY_ID)
+ @Param(description = "KMS key id of the volume", since = "4.23.0")
+ private String kmsKeyId;
+
+ @SerializedName(ApiConstants.KMS_KEY_VERSION)
+ @Param(description = "Version number of the KMS key used for disk encryption if applicable", since = "4.23.0")
+ private Integer kmsKeyVersion;
+
public String getPath() {
return path;
}
@@ -868,7 +880,35 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
this.volumeRepairResult = volumeRepairResult;
}
+ public String getEncryptionFormat() {
+ return encryptionFormat;
+ }
+
public void setEncryptionFormat(String encryptionFormat) {
this.encryptionFormat = encryptionFormat;
}
+
+ public String getKmsKey() {
+ return kmsKey;
+ }
+
+ public void setKmsKey(String kmsKey) {
+ this.kmsKey = kmsKey;
+ }
+
+ public String getKmsKeyId() {
+ return kmsKeyId;
+ }
+
+ public void setKmsKeyId(String kmsKeyId) {
+ this.kmsKeyId = kmsKeyId;
+ }
+
+ public Integer getKmsKeyVersion() {
+ return kmsKeyVersion;
+ }
+
+ public void setKmsKeyVersion(Integer kmsKeyVersion) {
+ this.kmsKeyVersion = kmsKeyVersion;
+ }
}
diff --git a/api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java b/api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java
new file mode 100644
index 00000000000..bbb8617ff24
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java
@@ -0,0 +1,46 @@
+// 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.kms;
+
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface HSMProfile extends Identity, InternalIdentity, ControlledEntity {
+ String getName();
+
+ String getProtocol();
+
+ long getAccountId();
+
+ long getDomainId();
+
+ Long getZoneId();
+
+ String getVendorName();
+
+ boolean isEnabled();
+
+ boolean getIsPublic();
+
+ Date getCreated();
+
+ Date getRemoved();
+}
diff --git a/api/src/main/java/org/apache/cloudstack/kms/KMSKey.java b/api/src/main/java/org/apache/cloudstack/kms/KMSKey.java
new file mode 100644
index 00000000000..c956a1ec66d
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/kms/KMSKey.java
@@ -0,0 +1,60 @@
+/*
+ * 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.kms;
+
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.cloudstack.framework.kms.KeyPurpose;
+
+import java.util.Date;
+
+/**
+ * KMS Key (Key Encryption Key) metadata.
+ * Represents a KEK that can be used to wrap/unwrap Data Encryption Keys (DEKs).
+ * KEKs are account-scoped and used for envelope encryption.
+ */
+public interface KMSKey extends Identity, InternalIdentity, ControlledEntity {
+
+ String getName();
+
+ String getDescription();
+
+ /**
+ * Provider-specific KEK label/ID (internal identifier used by the KMS provider)
+ */
+ String getKekLabel();
+
+ KeyPurpose getPurpose();
+
+ Long getZoneId();
+
+ String getAlgorithm();
+
+ Integer getKeyBits();
+
+ boolean isEnabled();
+
+ Date getCreated();
+
+ Date getRemoved();
+
+ Long getHsmProfileId();
+}
diff --git a/api/src/main/java/org/apache/cloudstack/kms/KMSManager.java b/api/src/main/java/org/apache/cloudstack/kms/KMSManager.java
new file mode 100644
index 00000000000..6e19010227d
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/kms/KMSManager.java
@@ -0,0 +1,292 @@
+// 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.kms;
+
+import com.cloud.storage.Volume;
+import com.cloud.user.Account;
+import com.cloud.utils.component.Manager;
+import org.apache.cloudstack.api.command.admin.kms.MigrateVolumesToKMSCmd;
+import org.apache.cloudstack.api.command.user.kms.RotateKMSKeyCmd;
+import org.apache.cloudstack.api.command.user.kms.CreateKMSKeyCmd;
+import org.apache.cloudstack.api.command.user.kms.DeleteKMSKeyCmd;
+import org.apache.cloudstack.api.command.user.kms.ListKMSKeysCmd;
+import org.apache.cloudstack.api.command.user.kms.UpdateKMSKeyCmd;
+import org.apache.cloudstack.api.command.user.kms.hsm.CreateHSMProfileCmd;
+import org.apache.cloudstack.api.command.user.kms.hsm.DeleteHSMProfileCmd;
+import org.apache.cloudstack.api.command.user.kms.hsm.ListHSMProfilesCmd;
+import org.apache.cloudstack.api.command.user.kms.hsm.UpdateHSMProfileCmd;
+import org.apache.cloudstack.api.response.HSMProfileResponse;
+import org.apache.cloudstack.api.response.KMSKeyResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.framework.kms.KMSProvider;
+import org.apache.cloudstack.framework.kms.WrappedKey;
+
+import java.util.List;
+
+public interface KMSManager extends Manager, Configurable {
+
+ ConfigKey KMSDekSizeBits = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.dek.size.bits",
+ "256",
+ "The size of Data Encryption Keys (DEK) in bits (128, 192, or 256)",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSRetryCount = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.retry.count",
+ "3",
+ "Number of retry attempts for transient KMS failures",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSRetryDelayMs = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.retry.delay.ms",
+ "1000",
+ "Delay in milliseconds between KMS retry attempts (exponential backoff)",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSOperationTimeoutSec = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.operation.timeout.sec",
+ "30",
+ "Timeout in seconds for KMS cryptographic operations",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSRewrapBatchSize = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.rewrap.batch.size",
+ "50",
+ "Number of wrapped keys to rewrap per batch in background job",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSRewrapIntervalMs = new ConfigKey<>(
+ "Advanced",
+ Long.class,
+ "kms.rewrap.interval.ms",
+ "300000",
+ "Interval in milliseconds between background rewrap job executions (default: 5 minutes)",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSOperationPoolCoreSize = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.operation.pool.core.size",
+ "2",
+ "Minimum number of threads kept alive for KMS cryptographic operations",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ ConfigKey KMSOperationPoolMaxSize = new ConfigKey<>(
+ "Advanced",
+ Integer.class,
+ "kms.operation.pool.max.size",
+ "100",
+ "Maximum number of concurrent threads for KMS cryptographic operations. " +
+ "Set this to match the concurrency limit of your HSM appliance or external KMS provider.",
+ true,
+ ConfigKey.Scope.Global
+ );
+
+ /**
+ * List all registered KMS providers
+ *
+ * @return list of available providers
+ */
+ List extends KMSProvider> listKMSProviders();
+
+ /**
+ * Get a specific KMS provider by name
+ *
+ * @param name provider name
+ * @return the provider, or null if not found
+ */
+ KMSProvider getKMSProvider(String name);
+
+ /**
+ * Check if caller has permission to use a KMS key
+ *
+ * @param callerAccountId the caller's account ID
+ * @param key the KMS key
+ * @return true if caller has permission
+ */
+ boolean hasPermission(Long callerAccountId, KMSKey key);
+
+ /**
+ * Validates that the KMS key can be used for volume encryption: key exists, not deleted,
+ * owner account matches the volume owner, key state is Enabled, and key purpose is VOLUME_ENCRYPTION.
+ * No-op if kmsKeyId is null.
+ *
+ * @param owner the account that will own the volume
+ * @param kmsKeyId the KMS key database ID
+ * @param zoneId the zone ID of the target resource (volume/VM)
+ * @throws InvalidParameterValueException if key not found, disabled, wrong purpose, zone mismatch, or account mismatch
+ */
+ void checkKmsKeyForVolumeEncryption(Account owner, Long kmsKeyId, Long zoneId);
+
+ /**
+ * Unwrap a DEK by wrapped key ID, trying multiple KEK versions if needed
+ *
+ * @param wrappedKeyId the wrapped key database ID
+ * @return plaintext DEK (caller must zeroize!)
+ * @throws KMSException if unwrap fails
+ */
+ byte[] unwrapKey(Long wrappedKeyId) throws KMSException;
+
+ /**
+ * Generate and wrap a DEK using a specific KMS key UUID
+ *
+ * @param kmsKey the KMS key
+ * @param callerAccountId the caller's account ID
+ * @return wrapped key ready for database storage
+ * @throws KMSException if operation fails
+ */
+ WrappedKey generateVolumeKeyWithKek(KMSKey kmsKey, Long callerAccountId) throws KMSException;
+
+ /**
+ * Create a KMS key and return the response object.
+ * Handles validation, account resolution, and permission checks.
+ *
+ * @param cmd the create command with all parameters
+ * @return KMSKeyResponse
+ * @throws KMSException if creation fails
+ */
+ KMSKeyResponse createKMSKey(CreateKMSKeyCmd cmd) throws KMSException;
+
+ /**
+ * List KMS keys and return the response object.
+ * Handles validation and permission checks.
+ *
+ * @param cmd the list command with all parameters
+ * @return ListResponse with KMSKeyResponse objects
+ */
+ ListResponse listKMSKeys(ListKMSKeysCmd cmd);
+
+ /**
+ * Update a KMS key and return the response object.
+ * Handles validation and permission checks.
+ *
+ * @param cmd the update command with all parameters
+ * @return KMSKeyResponse
+ * @throws KMSException if update fails
+ */
+ KMSKeyResponse updateKMSKey(UpdateKMSKeyCmd cmd) throws KMSException;
+
+ boolean deleteKMSWrappedKey(Volume vol) throws KMSException;
+
+ /**
+ * Delete a KMS key and return the response object.
+ * Handles validation and permission checks.
+ *
+ * @param cmd the delete command with all parameters
+ * @return SuccessResponse
+ * @throws KMSException if deletion fails
+ */
+ SuccessResponse deleteKMSKey(DeleteKMSKeyCmd cmd) throws KMSException;
+
+ /**
+ * Rotate KEK by creating new version and scheduling gradual re-encryption
+ *
+ * @param cmd the rotate command with all parameters
+ * @return New KEK version UUID
+ * @throws KMSException if rotation fails
+ */
+ String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException;
+
+ /**
+ * Migrate passphrase-based volumes to KMS encryption
+ *
+ * @param cmd the migrate command with all parameters
+ * @return Number of volumes successfully migrated
+ * @throws KMSException if migration fails
+ */
+ int migrateVolumesToKMS(MigrateVolumesToKMSCmd cmd) throws KMSException;
+
+ /**
+ * Delete all KMS keys owned by an account (called during account cleanup)
+ *
+ * @param accountId the account ID
+ * @return true if all keys were successfully deleted
+ */
+ boolean deleteKMSKeysByAccountId(Long accountId);
+
+ /**
+ * Add a new HSM profile
+ *
+ * @param cmd the add command
+ * @return the created HSM profile
+ * @throws KMSException if addition fails
+ */
+ HSMProfile addHSMProfile(CreateHSMProfileCmd cmd) throws KMSException;
+
+ /**
+ * List HSM profiles
+ *
+ * @param cmd the list command
+ * @return list of HSM profiles
+ */
+ ListResponse listHSMProfiles(ListHSMProfilesCmd cmd);
+
+ /**
+ * Delete an HSM profile
+ *
+ * @param cmd the delete command
+ * @return true if deletion was successful
+ * @throws KMSException if deletion fails
+ */
+ boolean deleteHSMProfile(DeleteHSMProfileCmd cmd) throws KMSException;
+
+ /**
+ * Update an HSM profile
+ *
+ * @param cmd the update command
+ * @return the updated HSM profile
+ * @throws KMSException if update fails
+ */
+ HSMProfile updateHSMProfile(UpdateHSMProfileCmd cmd) throws KMSException;
+
+ /**
+ * Create a response object for an HSM profile
+ *
+ * @param profile the HSM profile
+ * @return the response object
+ */
+ HSMProfileResponse createHSMProfileResponse(HSMProfile profile);
+}
diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java
similarity index 66%
rename from api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java
rename to api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java
index b1e7df3c39c..4bd39211a27 100644
--- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java
+++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java
@@ -16,20 +16,31 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.cloudstack.vm.schedule;
+package org.apache.cloudstack.schedule;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.time.ZoneId;
import java.util.Date;
-public interface VMSchedule extends Identity, InternalIdentity {
- enum Action {
- START, STOP, REBOOT, FORCE_STOP, FORCE_REBOOT
+public interface ResourceSchedule extends Identity, InternalIdentity {
+
+ /**
+ * Common contract for scheduler actions. Each provider defines its own enum
+ * implementing this interface so the generic machinery can call {@link #name()}
+ * and {@link #getEventType()} without knowing the concrete type.
+ */
+ interface Action {
+ String name();
+
+ String getEventType();
}
- long getVmId();
+ ApiCommandResourceType getResourceType();
+
+ long getResourceId();
String getDescription();
@@ -37,7 +48,7 @@ public interface VMSchedule extends Identity, InternalIdentity {
String getTimeZone();
- Action getAction();
+ String getActionName();
boolean getEnabled();
diff --git a/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java
new file mode 100644
index 00000000000..fb650a6cf68
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java
@@ -0,0 +1,45 @@
+/*
+ * 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.schedule;
+
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+public interface ResourceScheduleManager {
+
+ ResourceScheduleResponse createSchedule(ApiCommandResourceType resourceType, String resourceUuid,
+ String description, String schedule, String timeZone, String action,
+ Date startDate, Date endDate, boolean enabled, Map details);
+
+ ResourceScheduleResponse updateSchedule(Long id, String description, String schedule, String timeZone,
+ Date startDate, Date endDate, Boolean enabled, Map details);
+
+ ListResponse listSchedule(Long id, List ids, ApiCommandResourceType resourceType,
+ String resourceUuid, String action, Boolean enabled,
+ Long startIndex, Long pageSize);
+
+ Long removeSchedule(ApiCommandResourceType resourceType, String resourceUuid, Long id, List ids);
+
+ void removeSchedulesForResource(ApiCommandResourceType resourceType, long resourceId);
+}
diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java
similarity index 77%
rename from api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java
rename to api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java
index d7a18b76827..a7bf4d9aa05 100644
--- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java
+++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java
@@ -16,23 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.cloudstack.vm.schedule;
+package org.apache.cloudstack.schedule;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
-public interface VMScheduledJob extends Identity, InternalIdentity {
- long getVmId();
+public interface ResourceScheduledJob extends Identity, InternalIdentity {
+ ApiCommandResourceType getResourceType();
- long getVmScheduleId();
+ long getResourceId();
+
+ long getScheduleId();
Long getAsyncJobId();
void setAsyncJobId(long asyncJobId);
- VMSchedule.Action getAction();
+ String getActionName();
Date getScheduledTime();
}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java b/api/src/main/java/org/apache/cloudstack/schedule/autoscale/AutoScaleScheduleAction.java
similarity index 59%
rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java
rename to api/src/main/java/org/apache/cloudstack/schedule/autoscale/AutoScaleScheduleAction.java
index 835ac696f26..c05dafe8879 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java
+++ b/api/src/main/java/org/apache/cloudstack/schedule/autoscale/AutoScaleScheduleAction.java
@@ -16,21 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.cloudstack.vm.schedule.dao;
+package org.apache.cloudstack.schedule.autoscale;
-import com.cloud.utils.db.GenericDao;
-import org.apache.cloudstack.vm.schedule.VMScheduledJobVO;
+import com.cloud.event.EventTypes;
+import org.apache.cloudstack.schedule.ResourceSchedule;
-import java.util.Date;
-import java.util.List;
-
-public interface VMScheduledJobDao extends GenericDao {
-
- List listJobsToStart(Date currentTimestamp);
-
- int expungeJobsForSchedules(List scheduleId, Date dateAfter);
-
- int expungeJobsBefore(Date currentTimestamp);
-
- VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp);
+public enum AutoScaleScheduleAction implements ResourceSchedule.Action {
+ UPDATE {
+ @Override
+ public String getEventType() {
+ return EventTypes.EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE;
+ }
+ }
}
diff --git a/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java b/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java
new file mode 100644
index 00000000000..1648305803f
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java
@@ -0,0 +1,45 @@
+/*
+ * 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.schedule.vm;
+
+import com.cloud.event.EventTypes;
+import org.apache.cloudstack.schedule.ResourceSchedule;
+
+public enum VMScheduleAction implements ResourceSchedule.Action {
+ START {
+ @Override
+ public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_START; }
+ },
+ STOP {
+ @Override
+ public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_STOP; }
+ },
+ REBOOT {
+ @Override
+ public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_REBOOT; }
+ },
+ FORCE_STOP {
+ @Override
+ public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_STOP; }
+ },
+ FORCE_REBOOT {
+ @Override
+ public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_REBOOT; }
+ };
+}
diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java
deleted file mode 100644
index 6aca05b58d5..00000000000
--- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.vm.schedule;
-
-import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
-import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
-import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
-import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
-import org.apache.cloudstack.api.response.ListResponse;
-import org.apache.cloudstack.api.response.VMScheduleResponse;
-
-public interface VMScheduleManager {
- VMScheduleResponse createSchedule(CreateVMScheduleCmd createVMScheduleCmd);
-
- VMScheduleResponse createResponse(VMSchedule vmSchedule);
-
- ListResponse listSchedule(ListVMScheduleCmd listVMScheduleCmd);
-
- VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd);
-
- long removeScheduleByVmId(long vmId, boolean expunge);
-
- Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd);
-}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java
index 99bc9d2b3fb..ae848b2712b 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java
@@ -21,8 +21,9 @@ package org.apache.cloudstack.api.command.user.vm;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -34,9 +35,11 @@ import org.mockito.MockitoAnnotations;
public class CreateVMScheduleCmdTest {
@Mock
- public VMScheduleManager vmScheduleManager;
+ public ResourceScheduleManager resourceScheduleManager;
+
@Mock
public EntityManager entityManager;
+
@InjectMocks
private CreateVMScheduleCmd createVMScheduleCmd = new CreateVMScheduleCmd();
@@ -59,10 +62,19 @@ public class CreateVMScheduleCmdTest {
*/
@Test
public void testSuccessfulExecution() {
- VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
- Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenReturn(vmScheduleResponse);
+ ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse();
+ scheduleResponse.setId("schedule-uuid");
+ scheduleResponse.setResourceId("vm-uuid");
+ Mockito.when(resourceScheduleManager.createSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.anyBoolean(), Mockito.any()
+ )).thenReturn(scheduleResponse);
createVMScheduleCmd.execute();
- Assert.assertEquals(vmScheduleResponse, createVMScheduleCmd.getResponseObject());
+ VMScheduleResponse response = (VMScheduleResponse) createVMScheduleCmd.getResponseObject();
+ Assert.assertNotNull(response);
+ Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId"));
}
/**
@@ -72,7 +84,11 @@ public class CreateVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
- Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
+ Mockito.when(resourceScheduleManager.createSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.anyBoolean(), Mockito.any()
+ )).thenThrow(new InvalidParameterValueException("Invalid schedule"));
createVMScheduleCmd.execute();
}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java
index 1f764a84365..cec7503bd52 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java
@@ -23,8 +23,7 @@ import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
-import org.apache.cloudstack.vm.schedule.VMSchedule;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -36,7 +35,8 @@ import org.mockito.MockitoAnnotations;
public class DeleteVMScheduleCmdTest {
@Mock
- public VMScheduleManager vmScheduleManager;
+ public ResourceScheduleManager resourceScheduleManager;
+
@Mock
public EntityManager entityManager;
@@ -64,9 +64,11 @@ public class DeleteVMScheduleCmdTest {
public void testSuccessfulExecution() {
final SuccessResponse response = new SuccessResponse();
response.setResponseName(deleteVMScheduleCmd.getCommandName());
- response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+ response.setObjectName("vmschedule");
- Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(1L);
+ Mockito.when(resourceScheduleManager.removeSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenReturn(1L);
deleteVMScheduleCmd.execute();
SuccessResponse actualResponse = (SuccessResponse) deleteVMScheduleCmd.getResponseObject();
Assert.assertEquals(response.getResponseName(), actualResponse.getResponseName());
@@ -80,7 +82,9 @@ public class DeleteVMScheduleCmdTest {
*/
@Test(expected = ServerApiException.class)
public void testServerApiException() {
- Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(0L);
+ Mockito.when(resourceScheduleManager.removeSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenReturn(0L);
deleteVMScheduleCmd.execute();
}
@@ -91,7 +95,9 @@ public class DeleteVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
- Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
+ Mockito.when(resourceScheduleManager.removeSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenThrow(new InvalidParameterValueException("Invalid schedule"));
deleteVMScheduleCmd.execute();
}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java
index f5434de3581..5449be17a57 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java
@@ -20,8 +20,9 @@ package org.apache.cloudstack.api.command.user.vm;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -36,7 +37,8 @@ import java.util.Collections;
public class ListVMScheduleCmdTest {
@Mock
- public VMScheduleManager vmScheduleManager;
+ public ResourceScheduleManager resourceScheduleManager;
+
@InjectMocks
private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd();
private AutoCloseable closeable;
@@ -58,12 +60,14 @@ public class ListVMScheduleCmdTest {
*/
@Test
public void testEmptyResponse() {
- ListResponse response = new ListResponse();
- response.setResponses(new ArrayList());
- Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response);
+ ListResponse response = new ListResponse();
+ response.setResponses(new ArrayList());
+ Mockito.when(resourceScheduleManager.listSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenReturn(response);
listVMScheduleCmd.execute();
ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject();
- Assert.assertEquals(response, actualResponseObject);
Assert.assertEquals(0L, actualResponseObject.getResponses().size());
}
@@ -74,15 +78,22 @@ public class ListVMScheduleCmdTest {
*/
@Test
public void testNonEmptyResponse() {
- ListResponse listResponse = new ListResponse();
- VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class);
+ ListResponse listResponse = new ListResponse();
+ ResourceScheduleResponse response = new ResourceScheduleResponse();
+ response.setId("schedule-uuid");
+ response.setResourceId("vm-uuid");
listResponse.setResponses(Collections.singletonList(response));
- Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(listResponse);
+ Mockito.when(resourceScheduleManager.listSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenReturn(listResponse);
listVMScheduleCmd.execute();
ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject();
- Assert.assertEquals(listResponse, actualResponseObject);
Assert.assertEquals(1L, actualResponseObject.getResponses().size());
- Assert.assertEquals(response, actualResponseObject.getResponses().get(0));
+ Assert.assertEquals("schedule-uuid",
+ org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "id"));
+ Assert.assertEquals("vm-uuid",
+ org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "vmId"));
}
/**
@@ -92,7 +103,10 @@ public class ListVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
- Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
+ Mockito.when(resourceScheduleManager.listSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenThrow(InvalidParameterValueException.class);
listVMScheduleCmd.execute();
ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject();
}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java
index 2c6c485f25b..a24252f4a1d 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java
@@ -21,9 +21,11 @@ package org.apache.cloudstack.api.command.user.vm;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
-import org.apache.cloudstack.vm.schedule.VMSchedule;
-import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.schedule.ResourceSchedule;
+import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -35,9 +37,11 @@ import org.mockito.MockitoAnnotations;
public class UpdateVMScheduleCmdTest {
@Mock
- public VMScheduleManager vmScheduleManager;
+ public ResourceScheduleManager resourceScheduleManager;
+
@Mock
public EntityManager entityManager;
+
@InjectMocks
private UpdateVMScheduleCmd updateVMScheduleCmd = new UpdateVMScheduleCmd();
@@ -60,10 +64,18 @@ public class UpdateVMScheduleCmdTest {
*/
@Test
public void testSuccessfulExecution() {
- VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
- Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenReturn(vmScheduleResponse);
+ ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse();
+ scheduleResponse.setId("schedule-uuid");
+ scheduleResponse.setResourceId("vm-uuid");
+ Mockito.when(resourceScheduleManager.updateSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenReturn(scheduleResponse);
updateVMScheduleCmd.execute();
- Assert.assertEquals(vmScheduleResponse, updateVMScheduleCmd.getResponseObject());
+ VMScheduleResponse response = (VMScheduleResponse) updateVMScheduleCmd.getResponseObject();
+ Assert.assertNotNull(response);
+ Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id"));
+ Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId"));
}
/**
@@ -73,7 +85,10 @@ public class UpdateVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
- Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
+ Mockito.when(resourceScheduleManager.updateSchedule(
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
+ )).thenThrow(new InvalidParameterValueException("Invalid schedule"));
updateVMScheduleCmd.execute();
}
@@ -84,11 +99,12 @@ public class UpdateVMScheduleCmdTest {
*/
@Test
public void testSuccessfulGetEntityOwnerId() {
- VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
+ ResourceSchedule schedule = Mockito.mock(ResourceSchedule.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
- Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(vmSchedule);
- Mockito.when(entityManager.findById(VirtualMachine.class, vmSchedule.getVmId())).thenReturn(vm);
+ Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
+ Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(schedule);
+ Mockito.when(entityManager.findById(VirtualMachine.class, schedule.getResourceId())).thenReturn(vm);
long ownerId = updateVMScheduleCmd.getEntityOwnerId();
Assert.assertEquals(vm.getAccountId(), ownerId);
@@ -101,8 +117,7 @@ public class UpdateVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testFailureGetEntityOwnerId() {
- VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
- Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null);
- long ownerId = updateVMScheduleCmd.getEntityOwnerId();
+ Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null);
+ updateVMScheduleCmd.getEntityOwnerId();
}
}
diff --git a/api/src/test/java/org/apache/cloudstack/api/response/AttachedIsoResponseTest.java b/api/src/test/java/org/apache/cloudstack/api/response/AttachedIsoResponseTest.java
new file mode 100644
index 00000000000..09d4eb598ab
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/response/AttachedIsoResponseTest.java
@@ -0,0 +1,46 @@
+// 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 org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class AttachedIsoResponseTest {
+
+ @Test
+ public void testFullConstructorPopulatesAllFields() {
+ AttachedIsoResponse response = new AttachedIsoResponse("uuid-1", "alpine-iso", "Alpine boot", 3, true);
+ Assert.assertEquals("uuid-1", response.getId());
+ Assert.assertEquals("alpine-iso", response.getName());
+ Assert.assertEquals("Alpine boot", response.getDisplayText());
+ Assert.assertEquals(Integer.valueOf(3), response.getDeviceSeq());
+ Assert.assertTrue(response.getBootable());
+ }
+
+ @Test
+ public void testNoArgConstructorLeavesFieldsNull() {
+ AttachedIsoResponse response = new AttachedIsoResponse();
+ Assert.assertNull(response.getId());
+ Assert.assertNull(response.getName());
+ Assert.assertNull(response.getDisplayText());
+ Assert.assertNull(response.getDeviceSeq());
+ Assert.assertNull(response.getBootable());
+ }
+}
diff --git a/client/pom.xml b/client/pom.xml
index b53563d61b3..90b839112ce 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -256,6 +256,16 @@
cloud-plugin-metrics
${project.version}
+
+ org.apache.cloudstack
+ cloud-plugin-kms-database
+ ${project.version}
+
+
+ org.apache.cloudstack
+ cloud-plugin-kms-pkcs11
+ ${project.version}
+
org.apache.cloudstack
cloud-plugin-network-nvp
diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
index 8bca42f16bc..0a92e8a637b 100644
--- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
+++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
@@ -367,5 +367,8 @@
+
+
+
diff --git a/core/src/main/resources/META-INF/cloudstack/kms/module.properties b/core/src/main/resources/META-INF/cloudstack/kms/module.properties
new file mode 100644
index 00000000000..98e38d7cd8f
--- /dev/null
+++ b/core/src/main/resources/META-INF/cloudstack/kms/module.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+name=kms
+parent=core
diff --git a/core/src/main/resources/META-INF/cloudstack/kms/spring-core-lifecycle-kms-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/kms/spring-core-lifecycle-kms-context-inheritable.xml
new file mode 100644
index 00000000000..9226eef8fc1
--- /dev/null
+++ b/core/src/main/resources/META-INF/cloudstack/kms/spring-core-lifecycle-kms-context-inheritable.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java
index a55219511cb..56624df1346 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java
@@ -120,7 +120,7 @@ public interface VolumeOrchestrationService {
void destroyVolume(Volume volume);
DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template,
- Account owner, Long deviceId, boolean incrementResourceCount);
+ Account owner, Long deviceId, Long kmsKeyId, boolean incrementResourceCount);
VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException;
@@ -150,7 +150,7 @@ public interface VolumeOrchestrationService {
* Allocate a volume or multiple volumes in case of template is registered with the 'deploy-as-is' option, allowing multiple disks
*/
List allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
- Account owner, Volume volume, Snapshot snapshot);
+ Account owner, Long kmsKeyId, Volume volume, Snapshot snapshot);
String getVmNameFromVolumeId(long volumeId);
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java
index 6be71b3cb25..887aeaef073 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java
@@ -71,7 +71,7 @@ public interface OrchestrationService {
@QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan,
@QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap,
@QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId,
- @QueryParam("root-disk-offering-id") Long rootDiskOfferingId, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
+ @QueryParam("root-disk-offering-id") Long rootDiskOfferingId, @QueryParam("root-disk-kms-key-id") Long rootDiskKmsKeyId, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
@POST
VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId,
@@ -80,7 +80,7 @@ public interface OrchestrationService {
@QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags,
@QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan,
@QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId,
- @QueryParam("data-disks-offering-info") List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
+ @QueryParam("root-disk-kms-key-id") Long rootDiskKmsKeyId, @QueryParam("data-disks-offering-info") List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
@POST
NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway);
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
index 269eb4f1c21..2f8d57171bc 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
@@ -67,6 +67,12 @@ public interface TemplateService {
void handleTemplateSync(DataStore store);
+ void enforceSecStorageCopyLimit(long templateId, long zoneId);
+
+ boolean canCopyTemplateToImageStore(long templateId, long zoneId);
+
+ void replicateTemplateUpToCap(long templateId, long zoneId);
+
void downloadBootstrapSysTemplate(DataStore store);
void addSystemVMTemplatesToSecondary(DataStore store);
diff --git a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java
index f1891c774ed..8c11fe6c93a 100644
--- a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java
+++ b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java
@@ -45,6 +45,8 @@ import com.cloud.vm.VirtualMachineProfile;
public interface TemplateManager {
static final String AllowPublicUserTemplatesCK = "allow.public.user.templates";
static final String TemplatePreloaderPoolSizeCK = "template.preloader.pool.size";
+ static final String PublicTemplateSecStorageCopyCK = "secstorage.public.template.copy.max";
+ static final String PrivateTemplateSecStorageCopyCK = "secstorage.private.template.copy.max";
static final ConfigKey AllowPublicUserTemplates = new ConfigKey("Advanced", Boolean.class, AllowPublicUserTemplatesCK, "true",
"If false, users will not be able to create public Templates.", true, ConfigKey.Scope.Account);
@@ -64,6 +66,33 @@ public interface TemplateManager {
true,
ConfigKey.Scope.Global);
+ ConfigKey PublicTemplateSecStorageCopy = new ConfigKey("Advanced", Integer.class,
+ PublicTemplateSecStorageCopyCK, "0",
+ "Maximum number of secondary storage pools to which a public template is copied. " +
+ "0 means copy to all secondary storage pools (default behavior).",
+ true, ConfigKey.Scope.Zone);
+
+ ConfigKey PrivateTemplateSecStorageCopy = new ConfigKey("Advanced", Integer.class,
+ PrivateTemplateSecStorageCopyCK, "1",
+ "Maximum number of secondary storage pools to which a private template is copied. " +
+ "Default is 1 to preserve existing behavior.",
+ true, ConfigKey.Scope.Zone);
+
+ ConfigKey VmIsoMaxCount = new ConfigKey("Advanced",
+ Integer.class,
+ "vm.iso.max.count", "1",
+ "Maximum number of ISOs that may be attached to a VM.",
+ true,
+ ConfigKey.Scope.Cluster);
+
+ // KVM/libvirt maps deviceSeq=3 to hdc (hda/hdb are taken by the root volume on i440fx/IDE).
+ // user_vm.iso_id has always pointed at this slot; additional cdroms live in vm_iso_map.
+ int CDROM_PRIMARY_DEVICE_SEQ = 3;
+
+ // Fallback per-VM cdrom cap when the placement host hasn't advertised host.cdrom.max.count
+ // (older agent, never-deployed VM, etc.).
+ int DEFAULT_CDROM_MAX_PER_VM = 1;
+
static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
static final String XS_TOOLS_ISO = "xs-tools.iso";
@@ -138,6 +167,12 @@ public interface TemplateManager {
List getImageStoreByTemplate(long templateId, Long zoneId);
+ /**
+ * Max number of secondary storage copies for the template in this zone; {@code 0} means no limit.
+ * SYSTEM/ROUTING/BUILTIN templates are always exempt (returns {@code 0}).
+ */
+ int getSecStorageCopyLimit(VMTemplateVO template, long zoneId);
+
TemplateInfo prepareIso(long isoId, long dcId, Long hostId, Long poolId);
diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
index c27240823b1..a232bdd05da 100755
--- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -601,7 +601,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
Long deviceId = dataDiskDeviceIds.get(index++);
String volumeName = deviceId == null ? "DATA-" + persistedVm.getId() : "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId);
volumeMgr.allocateRawVolume(Type.DATADISK, volumeName, dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(),
- dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, true);
+ dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, dataDiskOfferingInfo.getKmsKeyId(), true);
}
}
if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) {
@@ -611,7 +611,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024);
VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey());
volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf( diskNumber), diskOffering, diskOfferingSize, null, null,
- persistedVm, dataDiskTemplate, owner, diskNumber, true);
+ persistedVm, dataDiskTemplate, owner, diskNumber, null, true);
diskNumber++;
}
}
@@ -641,12 +641,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
String rootVolumeName = String.format("ROOT-%s", vm.getId());
if (template.getFormat() == ImageFormat.ISO) {
volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(),
- rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, true);
+ rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, rootDiskOfferingInfo.getKmsKeyId(), true);
} else if (Arrays.asList(ImageFormat.BAREMETAL, ImageFormat.EXTERNAL).contains(template.getFormat())) {
logger.debug("{} has format [{}]. Skipping ROOT volume [{}] allocation.", template, template.getFormat(), rootVolumeName);
} else {
volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal,
- rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, volume, snapshot);
+ rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, rootDiskOfferingInfo.getKmsKeyId(), volume, snapshot);
}
} finally {
// Remove volumeContext and pop vmContext back
diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java
index 964265cb873..9f6d02cc123 100644
--- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java
+++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java
@@ -165,7 +165,7 @@ public class CloudOrchestrator implements OrchestrationService {
public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu,
int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan,
Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId,
- List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException {
+ Long rootDiskKmsKeyId, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException {
// VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks,
// vmEntityManager);
@@ -199,6 +199,7 @@ public class CloudOrchestrator implements OrchestrationService {
}
rootDiskOfferingInfo.setDiskOffering(rootDiskOffering);
rootDiskOfferingInfo.setSize(rootDiskSize);
+ rootDiskOfferingInfo.setKmsKeyId(rootDiskKmsKeyId);
if (rootDiskOffering.isCustomizedIops() != null && rootDiskOffering.isCustomizedIops()) {
Map userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId());
@@ -281,7 +282,7 @@ public class CloudOrchestrator implements OrchestrationService {
@Override
public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os,
int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan,
- Map> extraDhcpOptionMap, Long diskOfferingId, List dataDiskInfoList, Volume volume, Snapshot snapshot)
+ Map> extraDhcpOptionMap, Long diskOfferingId, Long rootDiskKmsKeyId, List dataDiskInfoList, Volume volume, Snapshot snapshot)
throws InsufficientCapacityException {
// VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager);
@@ -317,6 +318,7 @@ public class CloudOrchestrator implements OrchestrationService {
rootDiskOfferingInfo.setDiskOffering(diskOffering);
rootDiskOfferingInfo.setSize(size);
+ rootDiskOfferingInfo.setKmsKeyId(rootDiskKmsKeyId);
if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) {
Map userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId());
diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
index c4a6a9dd684..b4c104da07e 100644
--- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
+++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
@@ -89,6 +89,13 @@ import org.apache.cloudstack.resourcelimit.Reserver;
import org.apache.cloudstack.secret.PassphraseVO;
import org.apache.cloudstack.secret.dao.PassphraseDao;
import org.apache.cloudstack.snapshot.SnapshotHelper;
+import org.apache.cloudstack.kms.KMSManager;
+import org.apache.cloudstack.kms.KMSKeyVO;
+import org.apache.cloudstack.kms.KMSWrappedKeyVO;
+import org.apache.cloudstack.kms.dao.KMSKeyDao;
+import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
+import org.apache.cloudstack.framework.kms.KMSException;
+import org.apache.cloudstack.framework.kms.WrappedKey;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@@ -287,6 +294,12 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
@Inject
private DataStoreProviderManager dataStoreProviderMgr;
+ @Inject
+ private KMSManager kmsManager;
+ @Inject
+ private KMSKeyDao kmsKeyDao;
+ @Inject
+ private KMSWrappedKeyDao kmsWrappedKeyDao;
private final StateMachine2 _volStateMachine;
protected List _storagePoolAllocators;
@@ -323,7 +336,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
// Find a destination storage pool with the specified criteria
DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volumeInfo.getDiskOfferingId());
DiskProfile dskCh = new DiskProfile(volumeInfo.getId(), volumeInfo.getVolumeType(), volumeInfo.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(),
- diskOffering.isUseLocalStorage(), diskOffering.isRecreatable(), null, (diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null));
+ diskOffering.isUseLocalStorage(), diskOffering.isRecreatable(), null, (diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null));
dskCh.setHyperType(dataDiskHyperType);
storageMgr.setDiskProfileThrottling(dskCh, null, diskOffering);
@@ -358,9 +371,13 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
newVol.setInstanceId(oldVol.getInstanceId());
newVol.setRecreatable(oldVol.isRecreatable());
newVol.setFormat(oldVol.getFormat());
- if ((diskOffering == null || diskOffering.getEncrypt()) && oldVol.getPassphraseId() != null) {
- PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true));
- newVol.setPassphraseId(passphrase.getId());
+ if ((diskOffering == null || diskOffering.getEncrypt())) {
+ if (oldVol.getKmsKeyId() != null) {
+ newVol.setKmsKeyId(oldVol.getKmsKeyId());
+ } else if (oldVol.getPassphraseId() != null) {
+ PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true));
+ newVol.setPassphraseId(passphrase.getId());
+ }
}
return _volsDao.persist(newVol);
@@ -515,7 +532,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId());
if (diskOffering.getEncrypt()) {
VolumeVO vol = (VolumeVO) volume;
- volume = setPassphraseForVolumeEncryption(vol);
+ // Retrieve KMS key from volume's kmsKeyId if provided
+ KMSKeyVO kmsKey = getKmsKeyFromVolume(vol);
+ volume = setPassphraseForVolumeEncryption(vol, kmsKey, volume.getAccountId());
}
DataCenter dc = _entityMgr.findById(DataCenter.class, volume.getDataCenterId());
DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType());
@@ -652,7 +671,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
}
protected DiskProfile createDiskCharacteristics(VolumeInfo volumeInfo, VirtualMachineTemplate template, DataCenter dc, DiskOffering diskOffering) {
- boolean requiresEncryption = diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null;
+ boolean requiresEncryption = diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null;
if (volumeInfo.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) {
String templateToString = getReflectOnlySelectedFields(template);
String zoneToString = getReflectOnlySelectedFields(dc);
@@ -732,7 +751,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
if (diskOffering.getEncrypt()) {
VolumeVO vol = _volsDao.findById(volumeInfo.getId());
- setPassphraseForVolumeEncryption(vol);
+ // Retrieve KMS key from volume's kmsKeyId if provided
+ KMSKeyVO kmsKey = getKmsKeyFromVolume(vol);
+ setPassphraseForVolumeEncryption(vol, kmsKey, vol.getAccountId());
volumeInfo = volFactory.getVolume(volumeInfo.getId());
}
}
@@ -996,8 +1017,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true)
@Override
- public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner,
- Long deviceId, boolean incrementResourceCount) {
+ public DiskProfile allocateRawVolume(
+ Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm,
+ VirtualMachineTemplate template, Account owner, Long deviceId, Long kmsKeyId, boolean incrementResourceCount
+ ) {
if (size == null) {
size = offering.getDiskSize();
} else {
@@ -1030,6 +1053,11 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
vol.setDisplayVolume(userVm.isDisplayVm());
}
+ // Set KMS key ID if provided
+ if (kmsKeyId != null) {
+ vol.setKmsKeyId(kmsKeyId);
+ }
+
vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType()));
vol = _volsDao.persist(vol);
@@ -1049,7 +1077,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
}
private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
- Account owner, long deviceId, String configurationId, Volume volume, Snapshot snapshot) {
+ Account owner, long deviceId, String configurationId, Long kmsKeyId, Volume volume, Snapshot snapshot) {
assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template.";
if (volume != null) {
@@ -1099,6 +1127,11 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
vol.setDisplayVolume(userVm.isDisplayVm());
}
+ // Set KMS key ID if provided
+ if (kmsKeyId != null) {
+ vol.setKmsKeyId(kmsKeyId);
+ }
+
vol = _volsDao.persist(vol);
saveVolumeDetails(offering.getId(), vol.getId());
@@ -1188,7 +1221,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true)
@Override
public List allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
- Account owner, Volume volume, Snapshot snapshot) {
+ Account owner, Long kmsKeyId, Volume volume, Snapshot snapshot) {
String templateToString = getReflectOnlySelectedFields(template);
int volumesNumber = 1;
@@ -1235,7 +1268,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
}
logger.info("Adding disk object [{}] to VM [{}]", volumeName, vm);
DiskProfile diskProfile = allocateTemplatedVolume(type, volumeName, offering, volumeSize, minIops, maxIops,
- template, vm, owner, deviceId, configurationId, volume, snapshot);
+ template, vm, owner, deviceId, configurationId, kmsKeyId, volume, snapshot);
profiles.add(diskProfile);
}
@@ -1945,7 +1978,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
if (vol.getState() == Volume.State.Allocated || vol.getState() == Volume.State.Creating) {
DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, vol.getDiskOfferingId());
if (diskOffering.getEncrypt()) {
- vol = setPassphraseForVolumeEncryption(vol);
+ // Retrieve KMS key from volume's kmsKeyId if provided
+ KMSKeyVO kmsKey = getKmsKeyFromVolume(vol);
+ vol = setPassphraseForVolumeEncryption(vol, kmsKey, vol.getAccountId());
}
newVol = vol;
} else {
@@ -2081,16 +2116,72 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
return new Pair<>(newVol, destPool);
}
- private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) {
- if (volume.getPassphraseId() != null) {
+ /**
+ * Helper method to retrieve KMS key from volume's kmsKeyId
+ */
+ private KMSKeyVO getKmsKeyFromVolume(VolumeVO volume) {
+ if (volume.getKmsKeyId() == null) {
+ return null;
+ }
+ return kmsKeyDao.findById(volume.getKmsKeyId());
+ }
+
+ private VolumeVO setKmsKeyForVolumeEncryption(VolumeVO volume, KMSKeyVO kmsKey, Long callerAccountId) {
+ // Determine caller account ID if not provided
+ if (callerAccountId == null) {
+ callerAccountId = volume.getAccountId();
+ }
+
+ // Validate permission
+ if (!kmsManager.hasPermission(callerAccountId, kmsKey)) {
+ throw new CloudRuntimeException("No permission to use KMS key: " + kmsKey);
+ }
+
+ try {
+ logger.debug("Generating and wrapping DEK for volume {} using KMS key {}", volume.getName(), kmsKey.getUuid());
+ long startTime = System.currentTimeMillis();
+
+ // Generate and wrap DEK using active KEK version
+ WrappedKey wrappedKey = kmsManager.generateVolumeKeyWithKek(kmsKey, callerAccountId);
+
+ // The wrapped key is already persisted by generateVolumeKeyWithKek, get its ID
+ KMSWrappedKeyVO wrappedKeyVO = kmsWrappedKeyDao.findByUuid(wrappedKey.getUuid());
+ if (wrappedKeyVO == null) {
+ throw new CloudRuntimeException("Failed to find persisted wrapped key: " + wrappedKey.getUuid());
+ }
+
+ // Set the wrapped key ID on the volume
+ volume.setKmsWrappedKeyId(wrappedKeyVO.getId());
+
+ long finishTime = System.currentTimeMillis();
+ logger.debug("Generating and persisting wrapped key took {} ms for volume: {}",
+ (finishTime - startTime), volume.getName());
+
+ return _volsDao.persist(volume);
+
+ } catch (KMSException e) {
+ throw new CloudRuntimeException("KMS failure while setting up volume encryption: " + e.getMessage(), e);
+ }
+ }
+ private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume, KMSKeyVO kmsKey, Long callerAccountId) {
+ // If volume already has encryption set up, return it
+ if (volume.getKmsWrappedKeyId() != null || volume.getPassphraseId() != null) {
return volume;
}
- logger.debug("Creating passphrase for the volume: " + volume.getName());
+ if (kmsKey != null) {
+ return setKmsKeyForVolumeEncryption(volume, kmsKey, callerAccountId);
+ }
+ // Legacy: passphrase-based encryption (fallback when KMS not enabled or KMS key not specified)
+ return setPassphraseForVolumeEncryption(volume);
+ }
+
+ private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) {
+ logger.debug("Creating passphrase for the volume: {}", volume.getName());
long startTime = System.currentTimeMillis();
PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true));
volume.setPassphraseId(passphrase.getId());
long finishTime = System.currentTimeMillis();
- logger.debug("Creating and persisting passphrase took: " + (finishTime - startTime) + " ms for the volume: " + volume.toString());
+ logger.debug("Creating and persisting passphrase took: {} ms for the volume: {}", finishTime - startTime, volume.toString());
return _volsDao.persist(volume);
}
@@ -2119,7 +2210,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver) store.getDriver();
long newSize = driver.getVolumeSizeRequiredOnPool(vol.getSize(),
template == null ? null : template.getSize(),
- vol.getPassphraseId() != null);
+ vol.getPassphraseId() != null || vol.getKmsKeyId() != null);
if (newSize == vol.getSize()) {
return;
diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java
index a184b4540a9..d61caf16ae5 100644
--- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java
+++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java
@@ -280,6 +280,7 @@ public class VolumeOrchestratorTest {
Mockito.when(oldVol.isRecreatable()).thenReturn(false);
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
Mockito.when(oldVol.getPassphraseId()).thenReturn(null); // no encryption
+ Mockito.when(oldVol.getKmsKeyId()).thenReturn(null); // no encryption
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
Mockito.when(volumeDao.persist(Mockito.any(VolumeVO.class))).thenReturn(persistedVol);
@@ -308,6 +309,7 @@ public class VolumeOrchestratorTest {
Mockito.when(oldVol.getInstanceId()).thenReturn(7L);
Mockito.when(oldVol.isRecreatable()).thenReturn(true);
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.RAW);
+ Mockito.when(oldVol.getKmsKeyId()).thenReturn(null);
Mockito.when(oldVol.getPassphraseId()).thenReturn(42L);
PassphraseVO passphrase = Mockito.mock(PassphraseVO.class);
@@ -341,9 +343,6 @@ public class VolumeOrchestratorTest {
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol);
- PassphraseVO mockPassPhrase = Mockito.mock(PassphraseVO.class);
- Mockito.when(passphraseDao.persist(Mockito.any())).thenReturn(mockPassPhrase);
-
VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, 222L);
assertNotNull(result);
}
diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml
index 654cd14a25d..664d4909e67 100644
--- a/engine/schema/pom.xml
+++ b/engine/schema/pom.xml
@@ -48,6 +48,11 @@
cloud-framework-db
${project.version}
+
+ org.apache.cloudstack
+ cloud-framework-kms
+ ${project.version}
+
com.mysql
mysql-connector-j
diff --git a/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java b/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java
index 94d01f472ba..97b7c54f084 100644
--- a/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java
@@ -20,6 +20,7 @@ import java.util.Date;
import java.util.List;
+import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.alert.AlertVO;
@@ -28,7 +29,7 @@ import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
-import com.cloud.utils.db.TransactionLegacy;
+import com.cloud.utils.db.UpdateBuilder;
@Component
public class AlertDaoImpl extends GenericDaoBase implements AlertDao {
@@ -107,25 +108,20 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert
}
sc.setParameters("archived", false);
- boolean result = true;
- ;
List alerts = listBy(sc);
if (ids != null && alerts.size() < ids.size()) {
- result = false;
- return result;
+ return false;
}
- if (alerts != null && !alerts.isEmpty()) {
- TransactionLegacy txn = TransactionLegacy.currentTxn();
- txn.start();
- for (AlertVO alert : alerts) {
- alert = lockRow(alert.getId(), true);
- alert.setArchived(true);
- update(alert.getId(), alert);
- txn.commit();
- }
- txn.close();
+
+ if (CollectionUtils.isEmpty(alerts)) {
+ return true;
}
- return result;
+
+ AlertVO alertForUpdate = createForUpdate();
+ alertForUpdate.setArchived(true);
+ UpdateBuilder ub = getUpdateBuilder(alertForUpdate);
+ update(ub, sc, null);
+ return true;
}
@Override
diff --git a/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java b/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java
index e748e98900e..9417ddd1259 100644
--- a/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java
@@ -18,8 +18,10 @@ package com.cloud.event.dao;
import java.util.Date;
import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.event.Event.State;
@@ -29,12 +31,13 @@ import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
-import com.cloud.utils.db.TransactionLegacy;
+import com.cloud.utils.db.UpdateBuilder;
@Component
public class EventDaoImpl extends GenericDaoBase implements EventDao {
protected final SearchBuilder CompletedEventSearch;
protected final SearchBuilder ToArchiveOrDeleteEventSearch;
+ protected final SearchBuilder ArchiveByIdsSearch;
public EventDaoImpl() {
CompletedEventSearch = createSearchBuilder();
@@ -51,6 +54,10 @@ public class EventDaoImpl extends GenericDaoBase implements Event
ToArchiveOrDeleteEventSearch.and("createdDateL", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ);
ToArchiveOrDeleteEventSearch.and("archived", ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ);
ToArchiveOrDeleteEventSearch.done();
+
+ ArchiveByIdsSearch = createSearchBuilder();
+ ArchiveByIdsSearch.and("id", ArchiveByIdsSearch.entity().getId(), Op.IN);
+ ArchiveByIdsSearch.done();
}
@Override
@@ -100,16 +107,16 @@ public class EventDaoImpl extends GenericDaoBase implements Event
@Override
public void archiveEvents(List events) {
- if (events != null && !events.isEmpty()) {
- TransactionLegacy txn = TransactionLegacy.currentTxn();
- txn.start();
- for (EventVO event : events) {
- event = lockRow(event.getId(), true);
- event.setArchived(true);
- update(event.getId(), event);
- txn.commit();
- }
- txn.close();
+ if (CollectionUtils.isEmpty(events)) {
+ return;
}
+
+ List ids = events.stream().map(EventVO::getId).collect(Collectors.toList());
+ SearchCriteria sc = ArchiveByIdsSearch.create();
+ sc.setParameters("id", ids.toArray(new Object[ids.size()]));
+ EventVO eventForUpdate = createForUpdate();
+ eventForUpdate.setArchived(true);
+ UpdateBuilder ub = getUpdateBuilder(eventForUpdate);
+ update(ub, sc, null);
}
}
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 cd4423dfa26..5b8a38b8e5b 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
@@ -646,16 +646,22 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao
sc.setParameters("lastPinged", lastPingSecondsAfter);
sc.setParameters("status", Status.Disconnected, Status.Down, Status.Alert);
- StringBuilder sb = new StringBuilder();
- List hosts = lockRows(sc, null, true); // exclusive lock
- for (HostVO host : hosts) {
- host.setManagementServerId(null);
- update(host.getId(), host);
- sb.append(host.getId());
- sb.append(" ");
+ // SELECT before bulk UPDATE to preserve per-host-ID trace logging — the bulk UPDATE
+ // cannot return which rows it matched since the WHERE column is being set to NULL
+ if (logger.isTraceEnabled()) {
+ List hosts = listBy(sc);
+ StringBuilder sb = new StringBuilder();
+ for (HostVO host : hosts) {
+ sb.append(host.getId());
+ sb.append(" ");
+ }
+ logger.trace("Following hosts will be reset: {}", sb);
}
- logger.trace("Following hosts got reset: {}", sb);
+ HostVO host = createForUpdate();
+ host.setManagementServerId(null);
+ UpdateBuilder ub = getUpdateBuilder(host);
+ update(ub, sc, null);
}
/*
diff --git a/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java b/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java
index ee5f67b09cd..23f0b0f15ae 100644
--- a/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java
@@ -16,6 +16,7 @@
// under the License.
package com.cloud.network;
+import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
@@ -25,8 +26,11 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
import com.cloud.network.rules.HealthCheckPolicy;
+import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
@Entity
@@ -68,6 +72,10 @@ public class LBHealthCheckPolicyVO implements HealthCheckPolicy {
@Column(name = "display", updatable = true, nullable = false)
protected boolean display = true;
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
protected LBHealthCheckPolicyVO() {
this.uuid = UUID.randomUUID().toString();
}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java
index 27bf7ba6aa8..57d53f92572 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java
@@ -94,6 +94,7 @@ public class FirewallRulesDaoImpl extends GenericDaoBase i
ReleaseSearch.and("ipId", ReleaseSearch.entity().getSourceIpAddressId(), Op.EQ);
ReleaseSearch.and("purpose", ReleaseSearch.entity().getPurpose(), Op.EQ);
ReleaseSearch.and("ports", ReleaseSearch.entity().getSourcePortStart(), Op.IN);
+ ReleaseSearch.and("removed", ReleaseSearch.entity().getRemoved(), Op.NULL);
ReleaseSearch.done();
SystemRuleSearch = createSearchBuilder();
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java
index 45927f3c377..a9b4e02ba87 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java
@@ -32,8 +32,9 @@ public class LBHealthCheckPolicyDaoImpl extends GenericDaoBase sc = createSearchCriteria();
sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
+ sc.addAnd("removed", SearchCriteria.Op.NULL);
- expunge(sc);
+ remove(sc);
}
@Override
@@ -41,8 +42,9 @@ public class LBHealthCheckPolicyDaoImpl extends GenericDaoBase sc = createSearchCriteria();
sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke);
+ sc.addAnd("removed", SearchCriteria.Op.NULL);
- expunge(sc);
+ remove(sc);
}
@Override
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java
index 6669d70824a..03fadbbd659 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java
@@ -28,4 +28,6 @@ public interface LBStickinessPolicyDao extends GenericDao listByLoadBalancerIdAndDisplayFlag(long loadBalancerId, boolean forDisplay);
List listByLoadBalancerId(long loadBalancerId, boolean revoke);
+
+ List listByLoadBalancerId(long loadBalancerId);
}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java
index 717458fdb99..86fd908b6b7 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java
@@ -31,8 +31,9 @@ public class LBStickinessPolicyDaoImpl extends GenericDaoBase sc = createSearchCriteria();
sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
+ sc.addAnd("removed", SearchCriteria.Op.NULL);
- expunge(sc);
+ remove(sc);
}
@Override
@@ -40,8 +41,9 @@ public class LBStickinessPolicyDaoImpl extends GenericDaoBase sc = createSearchCriteria();
sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke);
+ sc.addAnd("removed", SearchCriteria.Op.NULL);
- expunge(sc);
+ remove(sc);
}
@Override
@@ -62,4 +64,10 @@ public class LBStickinessPolicyDaoImpl extends GenericDaoBase listByLoadBalancerId(long loadBalancerId) {
+ SearchCriteria sc = createSearchCriteria();
+ sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
+ return listBy(sc);
+ }
}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java
index 72b8fc151b7..ae658f0757f 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java
@@ -17,6 +17,7 @@
package com.cloud.network.dao;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -30,9 +31,12 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
import com.cloud.network.rules.StickinessPolicy;
import com.cloud.utils.Pair;
+import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
@Entity
@@ -68,6 +72,10 @@ public class LBStickinessPolicyVO implements StickinessPolicy {
@Column(name = "display", updatable = true, nullable = false)
protected boolean display = true;
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
protected LBStickinessPolicyVO() {
this.uuid = UUID.randomUUID().toString();
}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java
index f95c61733f2..7fbe58df930 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java
@@ -16,13 +16,17 @@
// under the License.
package com.cloud.network.dao;
+import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.InternalIdentity;
@Entity
@@ -45,6 +49,10 @@ public class LoadBalancerCertMapVO implements InternalIdentity {
@Column(name = "revoke")
private boolean revoke = false;
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
public LoadBalancerCertMapVO() {
this.uuid = UUID.randomUUID().toString();
}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java
index dc37cdeefe3..03a1e45d917 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java
@@ -34,8 +34,9 @@ public class LoadBalancerVMMapDaoImpl extends GenericDaoBase sc = createSearchCriteria();
sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
+ sc.addAnd("removed", SearchCriteria.Op.NULL);
- expunge(sc);
+ remove(sc);
}
@Override
@@ -43,11 +44,12 @@ public class LoadBalancerVMMapDaoImpl extends GenericDaoBase sc = createSearchCriteria();
sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId);
sc.addAnd("instanceId", SearchCriteria.Op.IN, instanceIds.toArray());
+ sc.addAnd("removed", SearchCriteria.Op.NULL);
if (revoke != null) {
sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke);
}
- expunge(sc);
+ remove(sc);
}
@Override
@@ -56,12 +58,13 @@ public class LoadBalancerVMMapDaoImpl extends GenericDaoBase sc = VmIdSeqNumSearch.create();
sc.setParameters("vmId", vmId);
sc.setParameters("seqno", logSequenceNumber);
- final Filter filter = new Filter(SecurityGroupWorkVO.class, null, true, 0l, 1l);
-
- final List vos = lockRows(sc, filter, true);
- if (vos.size() == 0) {
- txn.commit();
- return;
- }
- SecurityGroupWorkVO work = vos.get(0);
- work.setStep(step);
- update(work.getId(), work);
-
- txn.commit();
+ SecurityGroupWorkVO workForUpdate = createForUpdate();
+ workForUpdate.setStep(step);
+ // LIMIT 1 preserves the original single-row semantics: op_nwgrp_work has no
+ // uniqueness on (instance_id, seq_no), so without it duplicate rows would all be updated.
+ update(workForUpdate, sc, 1);
}
@Override
@@ -172,21 +162,10 @@ public class SecurityGroupWorkDaoImpl extends GenericDaoBase, StateDao listVolumesByPassphraseId(long passphraseId);
+ /**
+ * List volumes with passphrase_id for migration to KMS
+ *
+ * @param zoneId Zone ID (required)
+ * @param accountId Account ID filter (optional, null for all accounts)
+ * @param domainId Domain ID filter (optional, null for all domains)
+ * @param limit Maximum number of volumes to return
+ * @return list of volumes that need migration
+ */
+ Pair, Integer> listVolumesForKMSMigration(Long zoneId, Long accountId, Long domainId, Integer limit);
+
/**
* Gets the Total Primary Storage space allocated for an account
*
@@ -169,6 +180,8 @@ public interface VolumeDao extends GenericDao, StateDao, StateDao findByKmsWrappedKeyId(Long kmsWrappedKeyId);
}
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java
index 91f1c7f5eb6..1d5ff5e9340 100644
--- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java
@@ -29,6 +29,7 @@ import javax.inject.Inject;
import org.apache.cloudstack.reservation.ReservationVO;
import org.apache.cloudstack.reservation.dao.ReservationDao;
+import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
@@ -80,11 +81,14 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol
protected GenericSearchBuilder secondaryStorageSearch;
private final SearchBuilder poolAndPathSearch;
final GenericSearchBuilder CountByOfferingId;
+ private final SearchBuilder kmsMigrationSearch;
@Inject
ReservationDao reservationDao;
@Inject
ResourceTagDao tagsDao;
+ @Inject
+ KMSWrappedKeyDao kmsWrappedKeyDao;
// need to account for zone-wide primary storage where storage_pool has
// null-value pod and cluster, where hypervisor information is stored in
@@ -412,6 +416,8 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol
AllFieldsSearch.and("passphraseId", AllFieldsSearch.entity().getPassphraseId(), Op.EQ);
AllFieldsSearch.and("iScsiName", AllFieldsSearch.entity().get_iScsiName(), Op.EQ);
AllFieldsSearch.and("path", AllFieldsSearch.entity().getPath(), Op.EQ);
+ AllFieldsSearch.and("kmsKeyId", AllFieldsSearch.entity().getKmsKeyId(), Op.EQ);
+ AllFieldsSearch.and("kmsWrappedKeyId", AllFieldsSearch.entity().getKmsWrappedKeyId(), Op.EQ);
AllFieldsSearch.done();
RootDiskStateSearch = createSearchBuilder();
@@ -528,6 +534,13 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol
CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId());
CountByOfferingId.and("diskOfferingId", CountByOfferingId.entity().getDiskOfferingId(), Op.EQ);
CountByOfferingId.done();
+
+ kmsMigrationSearch = createSearchBuilder();
+ kmsMigrationSearch.and("passphraseId", kmsMigrationSearch.entity().getPassphraseId(), Op.NNULL);
+ kmsMigrationSearch.and("zoneId", kmsMigrationSearch.entity().getDataCenterId(), Op.EQ);
+ kmsMigrationSearch.and("accountId", kmsMigrationSearch.entity().getAccountId(), Op.EQ);
+ kmsMigrationSearch.and("domainId", kmsMigrationSearch.entity().getDomainId(), Op.EQ);
+ kmsMigrationSearch.done();
}
@Override
@@ -748,6 +761,21 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol
return listBy(sc);
}
+ @Override
+ public Pair, Integer> listVolumesForKMSMigration(Long zoneId, Long accountId, Long domainId, Integer limit) {
+ SearchCriteria sc = kmsMigrationSearch.create();
+
+ Filter filter = new Filter(limit);
+ sc.setParameters("zoneId", zoneId);
+ if (accountId != null) {
+ sc.setParameters("accountId", accountId);
+ }
+ if (domainId != null) {
+ sc.setParameters("domainId", domainId);
+ }
+ return searchAndCount(sc, filter);
+ }
+
@Override
@DB
public boolean remove(Long id) {
@@ -756,6 +784,17 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol
logger.debug(String.format("Removing volume %s from DB", id));
VolumeVO entry = findById(id);
if (entry != null) {
+ // Clean up KMS wrapped key if volume was encrypted with KMS
+ if (entry.getKmsWrappedKeyId() != null) {
+ try {
+ kmsWrappedKeyDao.remove(entry.getKmsWrappedKeyId());
+ logger.debug("Removed KMS wrapped key [id={}] for volume [id={}, uuid={}]",
+ entry.getKmsWrappedKeyId(), id, entry.getUuid());
+ } catch (Exception e) {
+ logger.warn("Failed to remove KMS wrapped key [id={}] for volume [id={}, uuid={}]: {}",
+ entry.getKmsWrappedKeyId(), id, entry.getUuid(), e.getMessage(), e);
+ }
+ }
tagsDao.removeByIdAndType(id, ResourceObjectType.Volume);
}
boolean result = super.remove(id);
@@ -952,9 +991,24 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol
}
@Override
+ public boolean existsWithKmsKey(long kmsKeyId) {
+ SearchCriteria sc = AllFieldsSearch.create();
+ sc.setParameters("kmsKeyId", kmsKeyId);
+ sc.setParameters("notDestroyed", Volume.State.Expunged, Volume.State.Destroy);
+ return findOneBy(sc) != null;
+ }
+
public VolumeVO findByExternalUuid(String externalUuid) {
SearchCriteria sc = ExternalUuidSearch.create();
sc.setParameters("externalUuid", externalUuid);
return findOneBy(sc);
}
+
+ @Override
+ public List findByKmsWrappedKeyId(Long kmsWrappedKeyId) {
+ SearchCriteria sc = AllFieldsSearch.create();
+ sc.setParameters("kmsWrappedKeyId", kmsWrappedKeyId);
+ sc.setParameters("notDestroyed", Volume.State.Expunged);
+ return listBy(sc);
+ }
}
diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java
index 6f340501cf1..68ad8fa12ed 100644
--- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java
@@ -61,9 +61,6 @@ public class UsageJobDaoImpl extends GenericDaoBase implements
public void updateJobSuccess(Long jobId, long startMillis, long endMillis, long execTime, boolean success) {
TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB);
try {
- txn.start();
-
- UsageJobVO job = lockRow(jobId, Boolean.TRUE);
UsageJobVO jobForUpdate = createForUpdate();
jobForUpdate.setStartMillis(startMillis);
jobForUpdate.setEndMillis(endMillis);
@@ -71,11 +68,8 @@ public class UsageJobDaoImpl extends GenericDaoBase implements
jobForUpdate.setStartDate(new Date(startMillis));
jobForUpdate.setEndDate(new Date(endMillis));
jobForUpdate.setSuccess(success);
- update(job.getId(), jobForUpdate);
-
- txn.commit();
+ update(jobId, jobForUpdate);
} catch (Exception ex) {
- txn.rollback();
logger.error("error updating job success date", ex);
throw new CloudRuntimeException(ex.getMessage());
} finally {
diff --git a/engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java b/engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java
new file mode 100644
index 00000000000..f4a3f116818
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java
@@ -0,0 +1,83 @@
+// 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.vm;
+
+import java.util.Date;
+
+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 javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.cloudstack.api.InternalIdentity;
+
+@Entity
+@Table(name = "vm_iso_map")
+public class VmIsoMapVO implements InternalIdentity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "vm_id")
+ private long vmId;
+
+ @Column(name = "iso_id")
+ private long isoId;
+
+ @Column(name = "device_seq")
+ private int deviceSeq;
+
+ @Column(name = "created")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date created;
+
+ public VmIsoMapVO() {
+ }
+
+ public VmIsoMapVO(long vmId, long isoId, int deviceSeq) {
+ this.vmId = vmId;
+ this.isoId = isoId;
+ this.deviceSeq = deviceSeq;
+ this.created = new Date();
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ public long getVmId() {
+ return vmId;
+ }
+
+ public long getIsoId() {
+ return isoId;
+ }
+
+ public int getDeviceSeq() {
+ return deviceSeq;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java
new file mode 100644
index 00000000000..a472a3b4dec
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java
@@ -0,0 +1,34 @@
+// 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.vm.dao;
+
+import java.util.List;
+
+import com.cloud.utils.db.GenericDao;
+import com.cloud.vm.VmIsoMapVO;
+
+public interface VmIsoMapDao extends GenericDao {
+ List listByVmId(long vmId);
+
+ List listByIsoId(long isoId);
+
+ VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq);
+
+ VmIsoMapVO findByVmIdIsoId(long vmId, long isoId);
+
+ int removeByVmId(long vmId);
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java
new file mode 100644
index 00000000000..44749eea75f
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java
@@ -0,0 +1,92 @@
+// 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.vm.dao;
+
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.vm.VmIsoMapVO;
+
+@Component
+public class VmIsoMapDaoImpl extends GenericDaoBase implements VmIsoMapDao {
+
+ private SearchBuilder ListByVmId;
+ private SearchBuilder ListByIsoId;
+ private SearchBuilder ByVmIdDeviceSeq;
+ private SearchBuilder ByVmIdIsoId;
+
+ protected VmIsoMapDaoImpl() {
+ ListByVmId = createSearchBuilder();
+ ListByVmId.and("vmId", ListByVmId.entity().getVmId(), SearchCriteria.Op.EQ);
+ ListByVmId.done();
+
+ ListByIsoId = createSearchBuilder();
+ ListByIsoId.and("isoId", ListByIsoId.entity().getIsoId(), SearchCriteria.Op.EQ);
+ ListByIsoId.done();
+
+ ByVmIdDeviceSeq = createSearchBuilder();
+ ByVmIdDeviceSeq.and("vmId", ByVmIdDeviceSeq.entity().getVmId(), SearchCriteria.Op.EQ);
+ ByVmIdDeviceSeq.and("deviceSeq", ByVmIdDeviceSeq.entity().getDeviceSeq(), SearchCriteria.Op.EQ);
+ ByVmIdDeviceSeq.done();
+
+ ByVmIdIsoId = createSearchBuilder();
+ ByVmIdIsoId.and("vmId", ByVmIdIsoId.entity().getVmId(), SearchCriteria.Op.EQ);
+ ByVmIdIsoId.and("isoId", ByVmIdIsoId.entity().getIsoId(), SearchCriteria.Op.EQ);
+ ByVmIdIsoId.done();
+ }
+
+ @Override
+ public List listByVmId(long vmId) {
+ SearchCriteria sc = ListByVmId.create();
+ sc.setParameters("vmId", vmId);
+ return listBy(sc);
+ }
+
+ @Override
+ public List listByIsoId(long isoId) {
+ SearchCriteria sc = ListByIsoId.create();
+ sc.setParameters("isoId", isoId);
+ return listBy(sc);
+ }
+
+ @Override
+ public VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq) {
+ SearchCriteria sc = ByVmIdDeviceSeq.create();
+ sc.setParameters("vmId", vmId);
+ sc.setParameters("deviceSeq", deviceSeq);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public VmIsoMapVO findByVmIdIsoId(long vmId, long isoId) {
+ SearchCriteria sc = ByVmIdIsoId.create();
+ sc.setParameters("vmId", vmId);
+ sc.setParameters("isoId", isoId);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public int removeByVmId(long vmId) {
+ SearchCriteria sc = ListByVmId.create();
+ sc.setParameters("vmId", vmId);
+ return remove(sc);
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileDetailsVO.java
new file mode 100644
index 00000000000..cd20b8a74fe
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileDetailsVO.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.kms;
+
+import org.apache.cloudstack.api.ResourceDetail;
+
+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 = "kms_hsm_profile_details")
+public class HSMProfileDetailsVO implements ResourceDetail {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "profile_id")
+ private long resourceId;
+
+ @Column(name = "name")
+ private String name;
+
+ @Column(name = "value")
+ private String value;
+
+ public HSMProfileDetailsVO() {
+ }
+
+ public HSMProfileDetailsVO(long profileId, String name, String value) {
+ this.resourceId = profileId;
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public long getResourceId() {
+ return resourceId;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isDisplay() {
+ return true;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileVO.java
new file mode 100644
index 00000000000..86d57400c0d
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileVO.java
@@ -0,0 +1,183 @@
+// 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.kms;
+
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+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.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "kms_hsm_profiles")
+public class HSMProfileVO implements HSMProfile {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "name")
+ private String name;
+
+ @Column(name = "protocol")
+ private String protocol;
+
+ @Column(name = "account_id")
+ private Long accountId;
+
+ @Column(name = "domain_id")
+ private Long domainId;
+
+ @Column(name = "zone_id")
+ private Long zoneId;
+
+ @Column(name = "vendor_name")
+ private String vendorName;
+
+ @Column(name = "enabled")
+ private boolean enabled;
+
+ @Column(name = "is_public")
+ private boolean isPublic;
+
+ @Column(name = "created")
+ private Date created;
+
+ @Column(name = "removed")
+ private Date removed;
+
+ public HSMProfileVO() {
+ this.uuid = UUID.randomUUID().toString();
+ this.created = new Date();
+ this.isPublic = false;
+ }
+
+ public HSMProfileVO(String name, String protocol, Long accountId, Long domainId, Long zoneId, String vendorName) {
+ this.uuid = UUID.randomUUID().toString();
+ this.name = name;
+ this.protocol = protocol;
+ this.accountId = accountId;
+ this.domainId = domainId;
+ this.zoneId = zoneId;
+ this.vendorName = vendorName;
+ this.enabled = true;
+ this.isPublic = false;
+ this.created = new Date();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("HSMProfileVO %s",
+ ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
+ this, "id", "uuid", "name", "protocol", "system", "enabled"));
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getProtocol() {
+ return protocol;
+ }
+
+ @Override
+ public long getAccountId() {
+ return accountId == null ? -1 : accountId;
+ }
+
+ @Override
+ public long getDomainId() {
+ return domainId == null ? -1 : domainId;
+ }
+
+ @Override
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ @Override
+ public String getVendorName() {
+ return vendorName;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public Date getCreated() {
+ return created;
+ }
+
+ @Override
+ public Date getRemoved() {
+ return removed;
+ }
+
+ @Override
+ public Class> getEntityType() {
+ return HSMProfile.class;
+ }
+
+ public void setRemoved(Date removed) {
+ this.removed = removed;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setVendorName(String vendorName) {
+ this.vendorName = vendorName;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean getIsPublic() {
+ return isPublic;
+ }
+
+ public void setIsPublic(boolean isPublic) {
+ this.isPublic = isPublic;
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKekVersionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKekVersionVO.java
new file mode 100644
index 00000000000..1dacdff6802
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKekVersionVO.java
@@ -0,0 +1,193 @@
+// 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.kms;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * Database entity for KEK versions.
+ * Tracks multiple KEK versions per KMS key to support gradual rotation.
+ * During rotation, a new version is created (status=Active) and old versions
+ * are marked as Previous (still usable for decryption) or Archived (no longer used).
+ */
+@Entity
+@Table(name = "kms_kek_versions")
+public class KMSKekVersionVO {
+
+ public enum Status {
+ /**
+ * Used for new encryption operations
+ */
+ Active,
+ /**
+ * Still usable for decryption during key rotation
+ */
+ Previous,
+ /**
+ * No longer used; all wrapped keys have been re-encrypted
+ */
+ Archived
+ }
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "uuid", nullable = false)
+ private String uuid;
+
+ @Column(name = "kms_key_id", nullable = false)
+ private Long kmsKeyId;
+
+ @Column(name = "version_number", nullable = false)
+ private Integer versionNumber;
+
+ @Column(name = "kek_label", nullable = false)
+ private String kekLabel;
+
+ @Column(name = "status", nullable = false, length = 32)
+ @Enumerated(EnumType.STRING)
+ private Status status;
+
+ @Column(name = "hsm_profile_id")
+ private Long hsmProfileId;
+
+ @Column(name = GenericDao.CREATED_COLUMN, nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date created;
+
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date removed;
+
+ public KMSKekVersionVO(Long kmsKeyId, Integer versionNumber, String kekLabel) {
+ this();
+ this.kmsKeyId = kmsKeyId;
+ this.versionNumber = versionNumber;
+ this.kekLabel = kekLabel;
+ this.status = Status.Active;
+ }
+
+ public KMSKekVersionVO(Long kmsKeyId, String kekLabel) {
+ this();
+ this.kmsKeyId = kmsKeyId;
+ this.kekLabel = kekLabel;
+ this.status = Status.Active;
+ }
+
+ public KMSKekVersionVO() {
+ this.uuid = UUID.randomUUID().toString();
+ this.created = new Date();
+ this.status = Status.Active;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public Long getKmsKeyId() {
+ return kmsKeyId;
+ }
+
+ public void setKmsKeyId(Long kmsKeyId) {
+ this.kmsKeyId = kmsKeyId;
+ }
+
+ public Integer getVersionNumber() {
+ return versionNumber;
+ }
+
+ public void setVersionNumber(Integer versionNumber) {
+ this.versionNumber = versionNumber;
+ }
+
+ public String getKekLabel() {
+ return kekLabel;
+ }
+
+ public void setKekLabel(String kekLabel) {
+ this.kekLabel = kekLabel;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public void setStatus(Status status) {
+ this.status = status;
+ }
+
+ public Long getHsmProfileId() {
+ return hsmProfileId;
+ }
+
+ public void setHsmProfileId(Long hsmProfileId) {
+ this.hsmProfileId = hsmProfileId;
+ }
+
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public Date getRemoved() {
+ return removed;
+ }
+
+ public void setRemoved(Date removed) {
+ this.removed = removed;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("KMSKekVersion %s",
+ ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
+ this, "id", "uuid", "kmsKeyId", "versionNumber", "status", "kekLabel"));
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKeyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKeyVO.java
new file mode 100644
index 00000000000..36c68de3744
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKeyVO.java
@@ -0,0 +1,264 @@
+// 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.kms;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.framework.kms.KeyPurpose;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * Database entity for KMS Key (Key Encryption Key) metadata.
+ * Tracks ownership, purpose, and lifecycle of KEKs used in envelope encryption.
+ */
+@Entity
+@Table(name = "kms_keys")
+public class KMSKeyVO implements KMSKey {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "uuid", nullable = false)
+ private String uuid;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", length = 1024)
+ private String description;
+
+ @Column(name = "kek_label", nullable = false)
+ private String kekLabel;
+
+ @Column(name = "purpose", nullable = false, length = 32)
+ @Enumerated(EnumType.STRING)
+ private KeyPurpose purpose;
+
+ @Column(name = "account_id", nullable = false)
+ private Long accountId;
+
+ @Column(name = "domain_id", nullable = false)
+ private Long domainId;
+
+ @Column(name = "zone_id", nullable = false)
+ private Long zoneId;
+
+ @Column(name = "algorithm", nullable = false, length = 64)
+ private String algorithm;
+
+ @Column(name = "key_bits", nullable = false)
+ private Integer keyBits;
+
+ @Column(name = "enabled", nullable = false)
+ private boolean enabled;
+
+ @Column(name = "hsm_profile_id")
+ private Long hsmProfileId;
+
+ @Column(name = GenericDao.CREATED_COLUMN, nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date created;
+
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date removed;
+
+ public KMSKeyVO(String name, String description, String kekLabel,
+ KeyPurpose purpose, Long accountId, Long domainId,
+ Long zoneId, String algorithm, Integer keyBits
+ ) {
+ this();
+ this.name = name;
+ this.description = description;
+ this.kekLabel = kekLabel;
+ this.purpose = purpose;
+ this.accountId = accountId;
+ this.domainId = domainId;
+ this.zoneId = zoneId;
+ this.algorithm = algorithm;
+ this.keyBits = keyBits;
+ }
+
+ public KMSKeyVO() {
+ this.uuid = UUID.randomUUID().toString();
+ this.created = new Date();
+ this.enabled = true;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String getKekLabel() {
+ return kekLabel;
+ }
+
+ @Override
+ public KeyPurpose getPurpose() {
+ return purpose;
+ }
+
+ @Override
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ @Override
+ public Integer getKeyBits() {
+ return keyBits;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public Date getCreated() {
+ return created;
+ }
+
+ @Override
+ public Date getRemoved() {
+ return removed;
+ }
+
+ @Override
+ public Long getHsmProfileId() {
+ return hsmProfileId;
+ }
+
+ public void setHsmProfileId(Long hsmProfileId) {
+ this.hsmProfileId = hsmProfileId;
+ }
+
+ public void setRemoved(Date removed) {
+ this.removed = removed;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setKeyBits(Integer keyBits) {
+ this.keyBits = keyBits;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public void setZoneId(Long zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public void setPurpose(KeyPurpose purpose) {
+ this.purpose = purpose;
+ }
+
+ public void setKekLabel(String kekLabel) {
+ this.kekLabel = kekLabel;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ @Override
+ public long getDomainId() {
+ return domainId;
+ }
+
+ public void setDomainId(Long domainId) {
+ this.domainId = domainId;
+ }
+
+ @Override
+ public Class> getEntityType() {
+ return KMSKey.class;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("KMSKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
+ this, "id", "uuid", "name", "purpose",
+ "accountId", "zoneId", "enabled"
+ ));
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java
new file mode 100644
index 00000000000..8a763623fba
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java
@@ -0,0 +1,176 @@
+// 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.kms;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+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 javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * Database entity for storing wrapped (encrypted) Data Encryption Keys.
+ * Each entry represents a DEK that has been encrypted by a Key Encryption Key (KEK).
+ * KEK metadata is stored in kms_keys table via the kms_key_id foreign key.
+ */
+@Entity
+@Table(name = "kms_wrapped_key")
+public class KMSWrappedKeyVO {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "uuid", nullable = false)
+ private String uuid;
+
+ @Column(name = "kms_key_id")
+ private Long kmsKeyId;
+
+ @Column(name = "kek_version_id")
+ private Long kekVersionId;
+
+ @Column(name = "zone_id", nullable = false)
+ private Long zoneId;
+
+ @Column(name = "wrapped_blob", nullable = false)
+ private byte[] wrappedBlob;
+
+ @Column(name = GenericDao.CREATED_COLUMN, nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date created;
+
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date removed;
+
+ public KMSWrappedKeyVO(KMSKeyVO kmsKey, byte[] wrappedBlob) {
+ this();
+ this.kmsKeyId = kmsKey.getId();
+ this.zoneId = kmsKey.getZoneId();
+ this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
+ }
+
+ public KMSWrappedKeyVO() {
+ this.uuid = UUID.randomUUID().toString();
+ this.created = new Date();
+ }
+
+ public KMSWrappedKeyVO(KMSKeyVO kmsKey, Long kekVersionId, byte[] wrappedBlob) {
+ this();
+ this.kmsKeyId = kmsKey.getId();
+ this.kekVersionId = kekVersionId;
+ this.zoneId = kmsKey.getZoneId();
+ this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
+ }
+
+ public KMSWrappedKeyVO(Long kmsKeyId, Long zoneId, byte[] wrappedBlob) {
+ this();
+ this.kmsKeyId = kmsKeyId;
+ this.zoneId = zoneId;
+ this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
+ }
+
+ public KMSWrappedKeyVO(Long kmsKeyId, Long kekVersionId, Long zoneId, byte[] wrappedBlob) {
+ this();
+ this.kmsKeyId = kmsKeyId;
+ this.kekVersionId = kekVersionId;
+ this.zoneId = zoneId;
+ this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public Long getKmsKeyId() {
+ return kmsKeyId;
+ }
+
+ public void setKmsKeyId(Long kmsKeyId) {
+ this.kmsKeyId = kmsKeyId;
+ }
+
+ public Long getKekVersionId() {
+ return kekVersionId;
+ }
+
+ public void setKekVersionId(Long kekVersionId) {
+ this.kekVersionId = kekVersionId;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(Long zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public byte[] getWrappedBlob() {
+ return wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
+ }
+
+ public void setWrappedBlob(byte[] wrappedBlob) {
+ this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public Date getRemoved() {
+ return removed;
+ }
+
+ public void setRemoved(Date removed) {
+ this.removed = removed;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("KMSWrappedKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
+ this, "id", "uuid", "kmsKeyId", "kekVersionId", "zoneId", "created", "removed"));
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDao.java
new file mode 100644
index 00000000000..a2202a18bfc
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDao.java
@@ -0,0 +1,24 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.kms.HSMProfileVO;
+
+public interface HSMProfileDao extends GenericDao {
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDaoImpl.java
new file mode 100644
index 00000000000..b063d2cfe74
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDaoImpl.java
@@ -0,0 +1,29 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import org.apache.cloudstack.kms.HSMProfileVO;
+import org.springframework.stereotype.Component;
+
+@Component
+public class HSMProfileDaoImpl extends GenericDaoBase implements HSMProfileDao {
+ public HSMProfileDaoImpl() {
+ super();
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDao.java
new file mode 100644
index 00000000000..0d5c71b9e5f
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDao.java
@@ -0,0 +1,33 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.kms.HSMProfileDetailsVO;
+
+import java.util.List;
+
+public interface HSMProfileDetailsDao extends GenericDao {
+ List listByProfileId(long profileId);
+
+ void persist(long profileId, String name, String value);
+
+ HSMProfileDetailsVO findDetail(long profileId, String name);
+
+ void deleteDetails(long profileId);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDaoImpl.java
new file mode 100644
index 00000000000..59c0ec43259
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDaoImpl.java
@@ -0,0 +1,75 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.SearchCriteria.Op;
+import org.apache.cloudstack.kms.HSMProfileDetailsVO;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class HSMProfileDetailsDaoImpl extends GenericDaoBase implements HSMProfileDetailsDao {
+
+ protected SearchBuilder ProfileSearch;
+ protected SearchBuilder DetailSearch;
+
+ public HSMProfileDetailsDaoImpl() {
+ super();
+
+ ProfileSearch = createSearchBuilder();
+ ProfileSearch.and("profileId", ProfileSearch.entity().getResourceId(), Op.EQ);
+ ProfileSearch.done();
+
+ DetailSearch = createSearchBuilder();
+ DetailSearch.and("profileId", DetailSearch.entity().getResourceId(), Op.EQ);
+ DetailSearch.and("name", DetailSearch.entity().getName(), Op.EQ);
+ DetailSearch.done();
+ }
+
+ @Override
+ public List listByProfileId(long profileId) {
+ SearchCriteria sc = ProfileSearch.create();
+ sc.setParameters("profileId", profileId);
+ return listBy(sc);
+ }
+
+ @Override
+ public void persist(long profileId, String name, String value) {
+ HSMProfileDetailsVO vo = new HSMProfileDetailsVO(profileId, name, value);
+ persist(vo);
+ }
+
+ @Override
+ public HSMProfileDetailsVO findDetail(long profileId, String name) {
+ SearchCriteria sc = DetailSearch.create();
+ sc.setParameters("profileId", profileId);
+ sc.setParameters("name", name);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public void deleteDetails(long profileId) {
+ SearchCriteria sc = ProfileSearch.create();
+ sc.setParameters("profileId", profileId);
+ remove(sc);
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java
new file mode 100644
index 00000000000..dbe4ca5f648
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java
@@ -0,0 +1,43 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.kms.KMSKekVersionVO;
+
+import java.util.List;
+
+public interface KMSKekVersionDao extends GenericDao {
+
+ KMSKekVersionVO getActiveVersion(Long kmsKeyId);
+
+ /**
+ * Returns Active and Previous versions (usable for decryption)
+ */
+ List getVersionsForDecryption(Long kmsKeyId);
+
+ List listByKmsKeyId(Long kmsKeyId);
+
+ KMSKekVersionVO findByKmsKeyIdAndVersion(Long kmsKeyId, Integer versionNumber);
+
+ KMSKekVersionVO findByKekLabel(String kekLabel);
+
+ List findByStatus(KMSKekVersionVO.Status status);
+
+ List listByHsmProfileId(Long hsmProfileId);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java
new file mode 100644
index 00000000000..841ab8c03d9
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java
@@ -0,0 +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.kms.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.kms.KMSKekVersionVO;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class KMSKekVersionDaoImpl extends GenericDaoBase implements KMSKekVersionDao {
+
+ private final SearchBuilder allFieldSearch;
+
+ public KMSKekVersionDaoImpl() {
+ allFieldSearch = createSearchBuilder();
+ allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("status", allFieldSearch.entity().getStatus(), SearchCriteria.Op.IN);
+ allFieldSearch.and("versionNumber", allFieldSearch.entity().getVersionNumber(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("kekLabel", allFieldSearch.entity().getKekLabel(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("hsmProfileId", allFieldSearch.entity().getHsmProfileId(), SearchCriteria.Op.EQ);
+ allFieldSearch.done();
+ }
+
+ @Override
+ public KMSKekVersionVO getActiveVersion(Long kmsKeyId) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kmsKeyId", kmsKeyId);
+ sc.setParameters("status", KMSKekVersionVO.Status.Active);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public List getVersionsForDecryption(Long kmsKeyId) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kmsKeyId", kmsKeyId);
+ sc.setParameters("status", KMSKekVersionVO.Status.Active, KMSKekVersionVO.Status.Previous);
+ return listBy(sc);
+ }
+
+ @Override
+ public List listByKmsKeyId(Long kmsKeyId) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kmsKeyId", kmsKeyId);
+ return listBy(sc);
+ }
+
+ @Override
+ public KMSKekVersionVO findByKmsKeyIdAndVersion(Long kmsKeyId, Integer versionNumber) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kmsKeyId", kmsKeyId);
+ sc.setParameters("versionNumber", versionNumber);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public KMSKekVersionVO findByKekLabel(String kekLabel) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kekLabel", kekLabel);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public List findByStatus(KMSKekVersionVO.Status status) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("status", status);
+ return listBy(sc);
+ }
+
+ @Override
+ public List listByHsmProfileId(Long hsmProfileId) {
+ if (hsmProfileId == null) {
+ return new ArrayList<>();
+ }
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("hsmProfileId", hsmProfileId);
+ return listBy(sc);
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDao.java
new file mode 100644
index 00000000000..4a03f2ff96c
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDao.java
@@ -0,0 +1,35 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.framework.kms.KeyPurpose;
+import org.apache.cloudstack.kms.KMSKeyVO;
+
+import java.util.List;
+
+public interface KMSKeyDao extends GenericDao {
+
+ List listByAccount(Long accountId, KeyPurpose purpose, Boolean enabled);
+
+ List listByZone(Long zoneId, KeyPurpose purpose, Boolean enabled);
+
+ long countByHsmProfileId(Long hsmProfileId);
+
+ KMSKeyVO findByNameAndAccountId(String name, long accountId);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDaoImpl.java
new file mode 100644
index 00000000000..ac442c19d1a
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDaoImpl.java
@@ -0,0 +1,83 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.framework.kms.KeyPurpose;
+import org.apache.cloudstack.kms.KMSKeyVO;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class KMSKeyDaoImpl extends GenericDaoBase implements KMSKeyDao {
+
+ private final SearchBuilder allFieldSearch;
+
+ public KMSKeyDaoImpl() {
+ allFieldSearch = createSearchBuilder();
+ allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("kekLabel", allFieldSearch.entity().getKekLabel(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("domainId", allFieldSearch.entity().getDomainId(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("accountId", allFieldSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("purpose", allFieldSearch.entity().getPurpose(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("enabled", allFieldSearch.entity().isEnabled(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("hsmProfileId", allFieldSearch.entity().getHsmProfileId(), SearchCriteria.Op.EQ);
+ allFieldSearch.done();
+ }
+
+ @Override
+ public List listByAccount(Long accountId, KeyPurpose purpose, Boolean enabled) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("accountId", accountId);
+ sc.setParametersIfNotNull("purpose", purpose);
+ sc.setParametersIfNotNull("enabled", enabled);
+ return listBy(sc);
+ }
+
+ @Override
+ public List listByZone(Long zoneId, KeyPurpose purpose, Boolean enabled) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("zoneId", zoneId);
+ sc.setParametersIfNotNull("purpose", purpose);
+ sc.setParametersIfNotNull("enabled", enabled);
+ return listBy(sc);
+ }
+
+ @Override
+ public long countByHsmProfileId(Long hsmProfileId) {
+ if (hsmProfileId == null) {
+ return 0;
+ }
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("hsmProfileId", hsmProfileId);
+ Integer count = getCount(sc);
+ return count != null ? count : 0;
+ }
+
+ @Override
+ public KMSKeyVO findByNameAndAccountId(String name, long accountId) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("name", name);
+ sc.setParameters("accountId", accountId);
+ return findOneBy(sc);
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java
new file mode 100644
index 00000000000..5e9e418a166
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java
@@ -0,0 +1,35 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.kms.KMSWrappedKeyVO;
+
+import java.util.List;
+
+public interface KMSWrappedKeyDao extends GenericDao {
+
+ long countByKmsKeyId(Long kmsKeyId);
+
+ /**
+ * Limited variant for batch processing during key rotation
+ */
+ List listByKekVersionId(Long kekVersionId, int limit);
+
+ long countByKekVersionId(Long kekVersionId);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java
new file mode 100644
index 00000000000..fc1a7190ae8
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java
@@ -0,0 +1,70 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.kms.KMSWrappedKeyVO;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class KMSWrappedKeyDaoImpl extends GenericDaoBase implements KMSWrappedKeyDao {
+
+ private final SearchBuilder allFieldSearch;
+
+ public KMSWrappedKeyDaoImpl() {
+ super();
+
+ allFieldSearch = createSearchBuilder();
+ allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("kekVersionId", allFieldSearch.entity().getKekVersionId(), SearchCriteria.Op.EQ);
+ allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
+ allFieldSearch.done();
+ }
+
+ @Override
+ public long countByKmsKeyId(Long kmsKeyId) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kmsKeyId", kmsKeyId);
+ Integer count = getCount(sc);
+ return count != null ? count.longValue() : 0L;
+ }
+
+ @Override
+ public List listByKekVersionId(Long kekVersionId, int limit) {
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kekVersionId", kekVersionId);
+ Filter filter = new Filter(limit);
+ return listBy(sc, filter);
+ }
+
+ @Override
+ public long countByKekVersionId(Long kekVersionId) {
+ if (kekVersionId == null) {
+ return 0;
+ }
+ SearchCriteria sc = allFieldSearch.create();
+ sc.setParameters("kekVersionId", kekVersionId);
+ Integer count = getCount(sc);
+ return count != null ? count.longValue() : 0L;
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java
index 4ce7033156f..47e2d130d87 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java
@@ -17,6 +17,7 @@
package org.apache.cloudstack.region.gslb;
+import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
@@ -27,8 +28,11 @@ import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
import com.cloud.region.ha.GlobalLoadBalancerRule;
+import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
@Entity
@@ -74,6 +78,10 @@ public class GlobalLoadBalancerRuleVO implements GlobalLoadBalancerRule {
@Column(name = "state")
GlobalLoadBalancerRule.State state;
+ @Column(name = GenericDao.REMOVED_COLUMN)
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
public GlobalLoadBalancerRuleVO() {
uuid = UUID.randomUUID().toString();
}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java
new file mode 100644
index 00000000000..16985e6bac3
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java
@@ -0,0 +1,82 @@
+// 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.schedule;
+
+import org.apache.cloudstack.api.ResourceDetail;
+
+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 = "resource_schedule_details")
+public class ResourceScheduleDetailVO implements ResourceDetail {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "schedule_id")
+ private long resourceId;
+
+ @Column(name = "name")
+ private String name;
+
+ @Column(name = "value", length = 1024)
+ private String value;
+
+ @Column(name = "display")
+ private boolean display = true;
+
+ public ResourceScheduleDetailVO() {
+ }
+
+ public ResourceScheduleDetailVO(long scheduleId, String name, String value, boolean display) {
+ this.resourceId = scheduleId;
+ this.name = name;
+ this.value = value;
+ this.display = display;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public long getResourceId() {
+ return resourceId;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isDisplay() {
+ return display;
+ }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java
similarity index 71%
rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java
rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java
index e0065db1e77..33a09d79239 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java
@@ -16,9 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.cloudstack.vm.schedule;
+package org.apache.cloudstack.schedule;
import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import javax.persistence.Column;
@@ -37,8 +38,8 @@ import java.util.TimeZone;
import java.util.UUID;
@Entity
-@Table(name = "vm_schedule")
-public class VMScheduleVO implements VMSchedule {
+@Table(name = "resource_schedule")
+public class ResourceScheduleVO implements ResourceSchedule {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
@@ -50,8 +51,12 @@ public class VMScheduleVO implements VMSchedule {
@Column(name = "description")
String description;
- @Column(name = "vm_id", nullable = false)
- long vmId;
+ @Enumerated(value = EnumType.STRING)
+ @Column(name = "resource_type", nullable = false)
+ ApiCommandResourceType resourceType;
+
+ @Column(name = "resource_id", nullable = false)
+ long resourceId;
@Column(name = "schedule", nullable = false)
String schedule;
@@ -60,8 +65,7 @@ public class VMScheduleVO implements VMSchedule {
String timeZone;
@Column(name = "action", nullable = false)
- @Enumerated(value = EnumType.STRING)
- Action action;
+ String actionName;
@Column(name = "enabled", nullable = false)
boolean enabled;
@@ -80,17 +84,19 @@ public class VMScheduleVO implements VMSchedule {
@Column(name = GenericDao.REMOVED_COLUMN)
Date removed;
- public VMScheduleVO() {
+ public ResourceScheduleVO() {
uuid = UUID.randomUUID().toString();
}
- public VMScheduleVO(long vmId, String description, String schedule, String timeZone, Action action, Date startDate, Date endDate, boolean enabled) {
+ public ResourceScheduleVO(ApiCommandResourceType resourceType, long resourceId, String description, String schedule,
+ String timeZone, String action, Date startDate, Date endDate, boolean enabled) {
uuid = UUID.randomUUID().toString();
- this.vmId = vmId;
+ this.resourceType = resourceType;
+ this.resourceId = resourceId;
this.description = description;
this.schedule = schedule;
this.timeZone = timeZone;
- this.action = action;
+ this.actionName = action;
this.startDate = startDate;
this.endDate = endDate;
this.enabled = enabled;
@@ -98,7 +104,8 @@ public class VMScheduleVO implements VMSchedule {
@Override
public String toString() {
- return String.format("VMSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "uuid", "action", "description"));
+ return String.format("ResourceSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
+ this, "id", "uuid", "resourceType", "actionName", "description", "enabled"));
}
@Override
@@ -111,14 +118,25 @@ public class VMScheduleVO implements VMSchedule {
return id;
}
- public long getVmId() {
- return vmId;
+ @Override
+ public ApiCommandResourceType getResourceType() {
+ return resourceType;
}
- public void setVmId(long vmId) {
- this.vmId = vmId;
+ public void setResourceType(ApiCommandResourceType resourceType) {
+ this.resourceType = resourceType;
}
+ @Override
+ public long getResourceId() {
+ return resourceId;
+ }
+
+ public void setResourceId(long resourceId) {
+ this.resourceId = resourceId;
+ }
+
+ @Override
public String getDescription() {
return description;
}
@@ -127,6 +145,7 @@ public class VMScheduleVO implements VMSchedule {
this.description = description;
}
+ @Override
public String getSchedule() {
return schedule.substring(2);
}
@@ -144,14 +163,16 @@ public class VMScheduleVO implements VMSchedule {
this.timeZone = timeZone;
}
- public Action getAction() {
- return action;
+ @Override
+ public String getActionName() {
+ return actionName;
}
- public void setAction(Action action) {
- this.action = action;
+ public void setActionName(String action) {
+ this.actionName = action;
}
+ @Override
public boolean getEnabled() {
return enabled;
}
@@ -183,6 +204,7 @@ public class VMScheduleVO implements VMSchedule {
return TimeZone.getTimeZone(getTimeZone()).toZoneId();
}
+ @Override
public Date getCreated() {
return created;
}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java
similarity index 64%
rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java
rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java
index 775e9cfe40c..ca03056b67a 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java
@@ -16,8 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.cloudstack.vm.schedule;
+package org.apache.cloudstack.schedule;
+import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import javax.persistence.Column;
@@ -34,8 +35,8 @@ import java.util.Date;
import java.util.UUID;
@Entity
-@Table(name = "vm_scheduled_job")
-public class VMScheduledJobVO implements VMScheduledJob {
+@Table(name = "resource_scheduled_job")
+public class ResourceScheduledJobVO implements ResourceScheduledJob {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@@ -44,41 +45,44 @@ public class VMScheduledJobVO implements VMScheduledJob {
@Column(name = "uuid", nullable = false)
String uuid;
- @Column(name = "vm_id", nullable = false)
- long vmId;
+ @Enumerated(value = EnumType.STRING)
+ @Column(name = "resource_type", nullable = false)
+ ApiCommandResourceType resourceType;
- @Column(name = "vm_schedule_id", nullable = false)
- long vmScheduleId;
+ @Column(name = "resource_id", nullable = false)
+ long resourceId;
+
+ @Column(name = "schedule_id", nullable = false)
+ long scheduleId;
@Column(name = "async_job_id")
Long asyncJobId;
@Column(name = "action", nullable = false)
- @Enumerated(value = EnumType.STRING)
- VMSchedule.Action action;
+ String actionName;
@Column(name = "scheduled_timestamp")
@Temporal(value = TemporalType.TIMESTAMP)
Date scheduledTime;
- public VMScheduledJobVO() {
+ public ResourceScheduledJobVO() {
uuid = UUID.randomUUID().toString();
}
- public VMScheduledJobVO(long vmId, long vmScheduleId, VMSchedule.Action action, Date scheduledTime) {
+ public ResourceScheduledJobVO(ApiCommandResourceType resourceType, long resourceId, long scheduleId, String action, Date scheduledTime) {
uuid = UUID.randomUUID().toString();
- this.vmId = vmId;
- this.vmScheduleId = vmScheduleId;
- this.action = action;
+ this.resourceType = resourceType;
+ this.resourceId = resourceId;
+ this.scheduleId = scheduleId;
+ this.actionName = action;
this.scheduledTime = scheduledTime;
}
-
@Override
public String toString() {
- return String.format("VMScheduledJob %s",
+ return String.format("ResourceScheduledJob %s",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
- this, "id", "uuid", "action", "vmScheduleId", "vmId", "asyncJobId"));
+ this, "id", "uuid", "resourceType", "actionName", "scheduleId", "resourceId", "asyncJobId"));
}
@Override
@@ -92,13 +96,18 @@ public class VMScheduledJobVO implements VMScheduledJob {
}
@Override
- public long getVmId() {
- return vmId;
+ public ApiCommandResourceType getResourceType() {
+ return resourceType;
}
@Override
- public long getVmScheduleId() {
- return vmScheduleId;
+ public long getResourceId() {
+ return resourceId;
+ }
+
+ @Override
+ public long getScheduleId() {
+ return scheduleId;
}
@Override
@@ -112,8 +121,12 @@ public class VMScheduledJobVO implements VMScheduledJob {
}
@Override
- public VMSchedule.Action getAction() {
- return action;
+ public String getActionName() {
+ return actionName;
+ }
+
+ public void setActionName(String action) {
+ this.actionName = action;
}
@Override
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java
new file mode 100644
index 00000000000..ee56991fb9a
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java
@@ -0,0 +1,40 @@
+/*
+ * 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.schedule.dao;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.GenericDao;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.schedule.ResourceScheduleVO;
+
+import java.util.List;
+
+public interface ResourceScheduleDao extends GenericDao {
+ List listAllActiveSchedules(ApiCommandResourceType resourceType);
+
+ long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List ids);
+
+ long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId);
+
+ Pair, Integer> searchAndCount(List ids, ApiCommandResourceType resourceType, Long resourceId,
+ String action, Boolean enabled, Long offset, Long limit);
+
+ SearchCriteria