diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 7533e58d4f3..84514493e94 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -258,6 +258,7 @@ public class EventTypes { public static final String EVENT_VOLUME_UPDATE = "VOLUME.UPDATE"; public static final String EVENT_VOLUME_DESTROY = "VOLUME.DESTROY"; public static final String EVENT_VOLUME_RECOVER = "VOLUME.RECOVER"; + public static final String EVENT_VOLUME_CHANGE_DISK_OFFERING = "VOLUME.CHANGE.DISK.OFFERING"; // Domains public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE"; @@ -768,6 +769,7 @@ public class EventTypes { entityEventDetails.put(EVENT_VOLUME_RESIZE, Volume.class); entityEventDetails.put(EVENT_VOLUME_DESTROY, Volume.class); entityEventDetails.put(EVENT_VOLUME_RECOVER, Volume.class); + entityEventDetails.put(EVENT_VOLUME_CHANGE_DISK_OFFERING, Volume.class); // Domains entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class); diff --git a/api/src/main/java/com/cloud/offering/DiskOffering.java b/api/src/main/java/com/cloud/offering/DiskOffering.java index fd21118e4a2..8f2a0c9f761 100644 --- a/api/src/main/java/com/cloud/offering/DiskOffering.java +++ b/api/src/main/java/com/cloud/offering/DiskOffering.java @@ -34,10 +34,6 @@ public interface DiskOffering extends InfrastructureEntity, Identity, InternalId Inactive, Active, } - enum Type { - Disk, Service - }; - State getState(); enum DiskCacheMode { @@ -61,8 +57,6 @@ public interface DiskOffering extends InfrastructureEntity, Identity, InternalId String getName(); - boolean isSystemUse(); - String getDisplayText(); ProvisioningType getProvisioningType(); @@ -152,5 +146,7 @@ public interface DiskOffering extends InfrastructureEntity, Identity, InternalId void setCacheMode(DiskCacheMode cacheMode); - Type getType(); + boolean isComputeOnly(); + + boolean getDiskSizeStrictness(); } diff --git a/api/src/main/java/com/cloud/offering/ServiceOffering.java b/api/src/main/java/com/cloud/offering/ServiceOffering.java index 7bad77fef30..278acbe231e 100644 --- a/api/src/main/java/com/cloud/offering/ServiceOffering.java +++ b/api/src/main/java/com/cloud/offering/ServiceOffering.java @@ -25,37 +25,34 @@ import org.apache.cloudstack.api.InternalIdentity; /** * offered. */ -public interface ServiceOffering extends DiskOffering, InfrastructureEntity, InternalIdentity, Identity { - public static final String consoleProxyDefaultOffUniqueName = "Cloud.com-ConsoleProxy"; - public static final String ssvmDefaultOffUniqueName = "Cloud.com-SecondaryStorage"; - public static final String routerDefaultOffUniqueName = "Cloud.Com-SoftwareRouter"; - public static final String elbVmDefaultOffUniqueName = "Cloud.Com-ElasticLBVm"; - public static final String internalLbVmDefaultOffUniqueName = "Cloud.Com-InternalLBVm"; +public interface ServiceOffering extends InfrastructureEntity, InternalIdentity, Identity { + static final String consoleProxyDefaultOffUniqueName = "Cloud.com-ConsoleProxy"; + static final String ssvmDefaultOffUniqueName = "Cloud.com-SecondaryStorage"; + static final String routerDefaultOffUniqueName = "Cloud.Com-SoftwareRouter"; + static final String elbVmDefaultOffUniqueName = "Cloud.Com-ElasticLBVm"; + static final String internalLbVmDefaultOffUniqueName = "Cloud.Com-InternalLBVm"; // leaving cloud.com references as these are identifyers and no real world addresses (check against DB) - public enum StorageType { + enum State { + Inactive, Active, + } + + enum StorageType { local, shared } - @Override String getDisplayText(); - @Override Date getCreated(); - @Override - String getTags(); - /** * @return user readable description */ - @Override String getName(); /** * @return is this a system service offering */ - @Override boolean isSystemUse(); /** @@ -98,12 +95,6 @@ public interface ServiceOffering extends DiskOffering, InfrastructureEntity, Int */ Integer getMulticastRateMbps(); - /** - * @return whether or not the service offering requires local storage - */ - @Override - boolean isUseLocalStorage(); - /** * @return tag that should be present on the host needed, optional parameter */ @@ -117,5 +108,35 @@ public interface ServiceOffering extends DiskOffering, InfrastructureEntity, Int boolean isDynamic(); + void setState(ServiceOffering.State state); + + ServiceOffering.State getState(); + + void setName(String name); + + String getUniqueName(); + + void setUniqueName(String uniqueName); + + void setDisplayText(String displayText); + + boolean isCustomized(); + + void setCustomized(boolean customized); + + Date getRemoved(); + + void setRemoved(Date removed); + + void setSortKey(int key); + + int getSortKey(); + + Long getDiskOfferingId(); + boolean isDynamicScalingEnabled(); + + Boolean getDiskOfferingStrictness(); + + void setDiskOfferingStrictness(boolean diskOfferingStrictness); } diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java index 56f36a8d90c..a1112313961 100644 --- a/api/src/main/java/com/cloud/server/ManagementService.java +++ b/api/src/main/java/com/cloud/server/ManagementService.java @@ -394,7 +394,8 @@ public interface ManagementService { /** * List storage pools for live migrating of a volume. The API returns list of all pools in the cluster to which the - * volume can be migrated. Current pool is not included in the list. + * volume can be migrated. Current pool is not included in the list. In case of vSphere datastore cluster storage pools, + * this method removes the child storage pools and adds the corresponding parent datastore cluster for API response listing * * @param Long volumeId * @return Pair, List> List of storage pools in cluster and list @@ -402,6 +403,8 @@ public interface ManagementService { */ Pair, List> listStoragePoolsForMigrationOfVolume(Long volumeId); + Pair, List> listStoragePoolsForMigrationOfVolumeInternal(Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool); + String[] listEventTypes(); Pair, Integer> listHypervisorCapabilities(Long id, HypervisorType hypervisorType, String keyword, Long startIndex, diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 6087ece3610..516fe7722e9 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -22,6 +22,7 @@ import java.net.MalformedURLException; import java.util.Map; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd; import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd; @@ -152,4 +153,6 @@ public interface VolumeApiService { Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge); Volume recoverVolume(long volumeId); + + Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) 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 175a92afaf9..9de5ce6fefd 100644 --- a/api/src/main/java/com/cloud/vm/DiskProfile.java +++ b/api/src/main/java/com/cloud/vm/DiskProfile.java @@ -42,6 +42,8 @@ public class DiskProfile { private Long iopsReadRate; private Long iopsWriteRate; private String cacheMode; + private Long minIops; + private Long maxIops; private HypervisorType hyperType; @@ -227,4 +229,22 @@ public class DiskProfile { public String getCacheMode() { return cacheMode; } + + + public Long getMinIops() { + return minIops; + } + + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } + } diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index d6252c08475..8a1d6155e98 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -218,7 +218,7 @@ public interface UserVmService { String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFProperties, boolean dynamicScalingEnabled) throws InsufficientCapacityException, + Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -300,7 +300,7 @@ public interface UserVmService { HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFProperties, boolean dynamicScalingEnabled) throws InsufficientCapacityException, + Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -379,7 +379,7 @@ public interface UserVmService { String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String type) + Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String type, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java index 79154cbee19..7a0715f1ec9 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachine.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java @@ -323,8 +323,6 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, Partition, long getServiceOfferingId(); - Long getDiskOfferingId(); - Long getBackupOfferingId(); String getBackupExternalId(); 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 b9a809f9956..94e6a7deeb0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -112,12 +112,15 @@ public class ApiConstants { public static final String DISK = "disk"; public static final String DISK_OFFERING_ID = "diskofferingid"; public static final String NEW_DISK_OFFERING_ID = "newdiskofferingid"; + public static final String OVERRIDE_DISK_OFFERING_ID = "overridediskofferingid"; public static final String DISK_KBS_READ = "diskkbsread"; public static final String DISK_KBS_WRITE = "diskkbswrite"; public static final String DISK_IO_READ = "diskioread"; public static final String DISK_IO_WRITE = "diskiowrite"; public static final String DISK_IO_PSTOTAL = "diskiopstotal"; public static final String DISK_SIZE = "disksize"; + public static final String DISK_SIZE_STRICTNESS = "disksizestrictness"; + public static final String DISK_OFFERING_STRICTNESS = "diskofferingstrictness"; public static final String DOWNLOAD_DETAILS = "downloaddetails"; public static final String UTILIZATION = "utilization"; public static final String DRIVER = "driver"; @@ -589,6 +592,7 @@ public class ApiConstants { public static final String LIVE_MIGRATE = "livemigrate"; public static final String MIGRATE_ALLOWED = "migrateallowed"; public static final String MIGRATE_TO = "migrateto"; + public static final String AUTO_MIGRATE = "automigrate"; public static final String GUID = "guid"; public static final String VSWITCH_TYPE_GUEST_TRAFFIC = "guestvswitchtype"; public static final String VSWITCH_TYPE_PUBLIC_TRAFFIC = "publicvswitchtype"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index e7b46be0040..1992ed8caa1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -159,6 +159,9 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.STORAGE_POLICY, type = CommandType.UUID, entityType = VsphereStoragePoliciesResponse.class,required = false, description = "Name of the storage policy defined at vCenter, this is applicable only for VMware", since = "4.15") private Long storagePolicy; + @Parameter(name = ApiConstants.DISK_SIZE_STRICTNESS, type = CommandType.BOOLEAN, description = "To allow or disallow the resize operation on the disks created from this disk offering, if the flag is true then resize is not allowed", since = "4.17") + private Boolean diskSizeStrictness; + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "details to specify disk offering parameters", since = "4.16") private Map details; @@ -301,6 +304,11 @@ public class CreateDiskOfferingCmd extends BaseCmd { public Long getStoragePolicy() { return storagePolicy; } + + public boolean getDiskSizeStrictness() { + return diskSizeStrictness != null ? diskSizeStrictness : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 5976aa81301..4eadfcdff25 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -33,6 +33,7 @@ import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.VsphereStoragePoliciesResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.collections.CollectionUtils; @@ -210,13 +211,13 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.MAX_MEMORY, type = CommandType.INTEGER, - description = "The maximum memroy size of the custom service offering in MB", + description = "The maximum memory size of the custom service offering in MB", since = "4.13") private Integer maxMemory; @Parameter(name = ApiConstants.MIN_MEMORY, type = CommandType.INTEGER, - description = "The minimum memroy size of the custom service offering in MB", + description = "The minimum memory size of the custom service offering in MB", since = "4.13") private Integer minMemory; @@ -227,6 +228,20 @@ public class CreateServiceOfferingCmd extends BaseCmd { description = "true if virtual machine needs to be dynamically scalable of cpu or memory") protected Boolean isDynamicScalingEnabled; + @Parameter(name = ApiConstants.DISK_OFFERING_ID, + required = false, + type = CommandType.UUID, + entityType = DiskOfferingResponse.class, + description = "the ID of the disk offering to which service offering should be mapped", + since = "4.17") + private Long diskOfferingId; + + @Parameter(name = ApiConstants.DISK_OFFERING_STRICTNESS, + type = CommandType.BOOLEAN, + description = "True/False to indicate the strictness of the disk offering association with the compute offering. When set to true, override of disk offering is not allowed when VM is deployed and change disk offering is not allowed for the ROOT disk after the VM is deployed", + since = "4.17") + private Boolean diskOfferingStrictness; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -449,6 +464,14 @@ public class CreateServiceOfferingCmd extends BaseCmd { return isDynamicScalingEnabled == null ? true : isDynamicScalingEnabled; } + public Long getDiskOfferingId() { + return diskOfferingId; + } + + public boolean getDiskOfferingStrictness() { + return diskOfferingStrictness == null ? false : diskOfferingStrictness; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/MigrateVolumeCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/MigrateVolumeCmdByAdmin.java index 135c8fc8bd1..9dd544cae58 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/MigrateVolumeCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/MigrateVolumeCmdByAdmin.java @@ -26,4 +26,5 @@ import com.cloud.storage.Volume; @APICommand(name = "migrateVolume", description = "Migrate volume", responseObject = VolumeResponse.class, since = "3.0.0", responseView = ResponseView.Full, entityType = { Volume.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class MigrateVolumeCmdByAdmin extends MigrateVolumeCmd implements AdminCmd {} +public class MigrateVolumeCmdByAdmin extends MigrateVolumeCmd implements AdminCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java index 92b8676b469..91fa1f864dc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.offering; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.log4j.Logger; @@ -50,6 +52,12 @@ public class ListDiskOfferingsCmd extends BaseListDomainResourcesCmd { since = "4.13") private Long zoneId; + @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, description = "The ID of the volume, tags of the volume are used to filter the offerings", since = "4.17") + private Long volumeId; + + @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "The ID of the storage pool, tags of the storage pool are used to filter the offerings", since = "4.17") + private Long storagePoolId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -66,6 +74,14 @@ public class ListDiskOfferingsCmd extends BaseListDomainResourcesCmd { return zoneId; } + public Long getVolumeId() { + return volumeId; + } + + public Long getStoragePoolId() { + return storagePoolId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index dab181e7c1a..4a9862c3caa 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -239,6 +239,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG description = "true if virtual machine needs to be dynamically scalable") protected Boolean dynamicScalingEnabled; + @Parameter(name = ApiConstants.OVERRIDE_DISK_OFFERING_ID, type = CommandType.UUID, since = "4.17", entityType = DiskOfferingResponse.class, description = "the ID of the disk offering for the virtual machine to be used for root volume instead of the disk offering mapped in service offering." + + "In case of virtual machine deploying from ISO, then the diskofferingid specified for root volume is ignored and uses this override disk offering id") + private Long overrideDiskOfferingId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -637,6 +641,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG return dynamicScalingEnabled == null ? true : dynamicScalingEnabled; } + public Long getOverrideDiskOfferingId() { + return overrideDiskOfferingId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java index 4d05ce24979..8243e1cda7d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java @@ -70,6 +70,19 @@ public class ScaleVMCmd extends BaseAsyncCmd implements UserCmd { @Parameter(name = ApiConstants.DETAILS, type = BaseCmd.CommandType.MAP, description = "name value pairs of custom parameters for cpu,memory and cpunumber. example details[i].name=value") private Map details; + @Parameter(name = ApiConstants.MIN_IOPS, type = CommandType.LONG, required = false, description = "New minimum number of IOPS for the custom disk offering", since = "4.17") + private Long minIops; + + @Parameter(name = ApiConstants.MAX_IOPS, type = CommandType.LONG, required = false, description = "New maximum number of IOPS for the custom disk offering", since = "4.17") + private Long maxIops; + + @Parameter(name = ApiConstants.AUTO_MIGRATE, type = CommandType.BOOLEAN, required = false, description = "Flag for automatic migration of the root volume " + + "with new compute offering whenever migration is required to apply the offering", since = "4.17") + private Boolean autoMigrate; + + @Parameter(name = ApiConstants.SHRINK_OK, type = CommandType.BOOLEAN, required = false, description = "Verify OK to Shrink", since = "4.17") + private Boolean shrinkOk; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -97,9 +110,29 @@ public class ScaleVMCmd extends BaseAsyncCmd implements UserCmd { } } } + + if (shrinkOk != null) customparameterMap.put(ApiConstants.SHRINK_OK, String.valueOf(isShrinkOk())); + if (autoMigrate != null) customparameterMap.put(ApiConstants.AUTO_MIGRATE, String.valueOf(getAutoMigrate())); + if (getMinIops() != null) customparameterMap.put(ApiConstants.MIN_IOPS, String.valueOf(getMinIops())); + if (getMaxIops() != null) customparameterMap.put(ApiConstants.MAX_IOPS, String.valueOf(getMaxIops())); + return customparameterMap; } + public Long getMinIops() { + return minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public boolean getAutoMigrate() { + return autoMigrate == null ? true : autoMigrate; + } + + public boolean isShrinkOk() { return shrinkOk == null ? true : shrinkOk; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java index 8dc8f445cd4..13185d34c50 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java @@ -68,6 +68,19 @@ public class UpgradeVMCmd extends BaseCmd implements UserCmd { @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "name value pairs of custom parameters for cpu, memory and cpunumber. example details[i].name=value") private Map details; + @Parameter(name = ApiConstants.MIN_IOPS, type = CommandType.LONG, required = false, description = "New minimum number of IOPS for the custom disk offering", since = "4.17") + private Long minIops; + + @Parameter(name = ApiConstants.MAX_IOPS, type = CommandType.LONG, required = false, description = "New maximum number of IOPS for the custom disk offering", since = "4.17") + private Long maxIops; + + @Parameter(name = ApiConstants.AUTO_MIGRATE, type = CommandType.BOOLEAN, required = false, description = "Flag for automatic migration of the root volume " + + "with new compute offering whenever migration is required to apply the offering", since = "4.17") + private Boolean autoMigrate; + + @Parameter(name = ApiConstants.SHRINK_OK, type = CommandType.BOOLEAN, required = false, description = "Verify OK to Shrink", since = "4.17") + private Boolean shrinkOk; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -92,9 +105,29 @@ public class UpgradeVMCmd extends BaseCmd implements UserCmd { } } } + + if (shrinkOk != null) customparameterMap.put(ApiConstants.SHRINK_OK, String.valueOf(isShrinkOk())); + if (autoMigrate != null) customparameterMap.put(ApiConstants.AUTO_MIGRATE, String.valueOf(getAutoMigrate())); + if (getMinIops() != null) customparameterMap.put(ApiConstants.MIN_IOPS, String.valueOf(getMinIops())); + if (getMaxIops() != null) customparameterMap.put(ApiConstants.MAX_IOPS, String.valueOf(getMaxIops())); + return customparameterMap; } + public Long getMinIops() { + return minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public boolean getAutoMigrate() { + return autoMigrate == null ? true : autoMigrate; + } + + public boolean isShrinkOk() { return shrinkOk == null ? true : shrinkOk; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java new file mode 100644 index 00000000000..5a1d07ef2be --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java @@ -0,0 +1,155 @@ +// 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.volume; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.offering.DiskOffering; +import com.cloud.storage.Volume; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +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.VolumeResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = ChangeOfferingForVolumeCmd.APINAME, + description = "Change disk offering of the volume and also an option to auto migrate if required to apply the new disk offering", + responseObject = VolumeResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + authorized = { RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, + since = "4.17") +public class ChangeOfferingForVolumeCmd extends BaseAsyncCmd implements UserCmd { + public static final String APINAME = "changeOfferingForVolume"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, entityType = VolumeResponse.class, required = true, type = CommandType.UUID, description = "the ID of the volume") + private Long id; + + @Parameter(name = ApiConstants.DISK_OFFERING_ID, + entityType = DiskOfferingResponse.class, + type = CommandType.UUID, + required = true, + description = "new disk offering id") + private Long newDiskOfferingId; + + @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, required = false, description = "New volume size in GB for the custom disk offering") + private Long size; + + @Parameter(name = ApiConstants.MIN_IOPS, type = CommandType.LONG, required = false, description = "New minimum number of IOPS for the custom disk offering") + private Long minIops; + + @Parameter(name = ApiConstants.MAX_IOPS, type = CommandType.LONG, required = false, description = "New maximum number of IOPS for the custom disk offering") + private Long maxIops; + + @Parameter(name = ApiConstants.AUTO_MIGRATE, type = CommandType.BOOLEAN, required = false, description = "Flag for automatic migration of the volume " + + "with new disk offering whenever migration is required to apply the offering") + private Boolean autoMigrate; + + @Parameter(name = ApiConstants.SHRINK_OK, type = CommandType.BOOLEAN, required = false, description = "Verify OK to Shrink") + private Boolean shrinkOk; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public ChangeOfferingForVolumeCmd() {} + + public ChangeOfferingForVolumeCmd(Long volumeId, long newDiskOfferingId, Long minIops, Long maxIops, boolean autoMigrate, boolean shrinkOk) { + this.id = volumeId; + this.minIops = minIops; + this.maxIops = maxIops; + this.newDiskOfferingId = newDiskOfferingId; + this.autoMigrate = autoMigrate; + this.shrinkOk = shrinkOk; + } + + public Long getId() { + return id; + } + + public Long getNewDiskOfferingId() { + return newDiskOfferingId; + } + + public Long getSize() { + return size; + } + + public Long getMinIops() { + return minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public boolean getAutoMigrate() { + return autoMigrate == null ? true : autoMigrate; + } + + public boolean isShrinkOk() { + return shrinkOk == null ? false: shrinkOk; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VOLUME_CHANGE_DISK_OFFERING; + } + + @Override + public String getEventDescription() { + return "Changing Disk offering of Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()) + " to " + this._uuidMgr.getUuid(DiskOffering.class, getNewDiskOfferingId()); + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + CallContext.current().setEventDetails("Volume Id: " + getId()); + Volume result = _volumeService.changeDiskOfferingForVolume(this); + if (result != null) { + VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseObject.ResponseView.Restricted, result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to change disk offering of volume"); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseAsyncCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java index 44dd4bf8c39..d3183dbf318 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java @@ -25,6 +25,7 @@ import org.apache.cloudstack.api.Parameter; 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.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.VolumeResponse; @@ -50,8 +51,8 @@ public class MigrateVolumeCmd extends BaseAsyncCmd implements UserCmd { @Parameter(name = ApiConstants.LIVE_MIGRATE, type = CommandType.BOOLEAN, required = false, description = "if the volume should be live migrated when it is attached to a running vm") private Boolean liveMigrate; - @Parameter(name = ApiConstants.NEW_DISK_OFFERING_ID, type = CommandType.STRING, description = "The new disk offering ID that replaces the current one used by the volume. This new disk offering is used to better reflect the new storage where the volume is going to be migrated to.") - private String newDiskOfferingUuid; + @Parameter(name = ApiConstants.NEW_DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "The new disk offering ID that replaces the current one used by the volume. This new disk offering is used to better reflect the new storage where the volume is going to be migrated to.") + private Long newDiskOfferingId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -74,6 +75,14 @@ public class MigrateVolumeCmd extends BaseAsyncCmd implements UserCmd { return (liveMigrate != null) ? liveMigrate : false; } + public MigrateVolumeCmd() { + } + public MigrateVolumeCmd(Long volumeId, Long storageId, Long newDiskOfferingId, Boolean liveMigrate) { + this.volumeId = volumeId; + this.storageId = storageId; + this.newDiskOfferingId = newDiskOfferingId; + this.liveMigrate = liveMigrate; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -103,8 +112,8 @@ public class MigrateVolumeCmd extends BaseAsyncCmd implements UserCmd { return "Attempting to migrate volume Id: " + this._uuidMgr.getUuid(Volume.class, getVolumeId()) + " to storage pool Id: " + this._uuidMgr.getUuid(StoragePool.class, getStoragePoolId()); } - public String getNewDiskOfferingUuid() { - return newDiskOfferingUuid; + public Long getNewDiskOfferingId() { + return newDiskOfferingId; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java index 004a7027262..7c220f627c0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java @@ -155,6 +155,10 @@ public class DiskOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "the vsphere storage policy tagged to the disk offering in case of VMware", since = "4.15") private String vsphereStoragePolicy; + @SerializedName(ApiConstants.DISK_SIZE_STRICTNESS) + @Param(description = "To allow or disallow the resize operation on the disks created from this disk offering, if the flag is true then resize is not allowed", since = "4.17") + private Boolean diskSizeStrictness; + public Boolean getDisplayOffering() { return displayOffering; } @@ -363,4 +367,12 @@ public class DiskOfferingResponse extends BaseResponseWithAnnotations { public void setVsphereStoragePolicy(String vsphereStoragePolicy) { this.vsphereStoragePolicy = vsphereStoragePolicy; } + + public Boolean getDiskSizeStrictness() { + return diskSizeStrictness; + } + + public void setDiskSizeStrictness(Boolean diskSizeStrictness) { + this.diskSizeStrictness = diskSizeStrictness; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index ea9d8eef7a0..b65911e572c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -208,6 +208,24 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "true if virtual machine needs to be dynamically scalable of cpu or memory", since = "4.16") private Boolean dynamicScalingEnabled; + @SerializedName(ApiConstants.DISK_OFFERING_STRICTNESS) + @Param(description = "True/False to indicate the strictness of the disk offering association with the compute offering. " + + "When set to true, override of disk offering is not allowed when VM is deployed and " + + "change disk offering is not allowed for the ROOT disk after the VM is deployed", since = "4.17") + private Boolean diskOfferingStrictness; + + @SerializedName(ApiConstants.DISK_OFFERING_ID) + @Param(description = "the ID of the disk offering to which service offering is linked", since = "4.17") + private String diskOfferingId; + + @SerializedName("diskofferingname") + @Param(description = "name of the disk offering", since = "4.17") + private String diskOfferingName; + + @SerializedName("diskofferingdisplaytext") + @Param(description = "the display text of the disk offering", since = "4.17") + private String diskOfferingDisplayText; + public ServiceOfferingResponse() { } @@ -486,4 +504,36 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { this.dynamicScalingEnabled = dynamicScalingEnabled; } + + public Boolean getDiskOfferingStrictness() { + return diskOfferingStrictness; + } + + public void setDiskOfferingStrictness(Boolean diskOfferingStrictness) { + this.diskOfferingStrictness = diskOfferingStrictness; + } + + public void setDiskOfferingId(String diskOfferingId) { + this.diskOfferingId = diskOfferingId; + } + + public void setDiskOfferingName(String diskOfferingName) { + this.diskOfferingName = diskOfferingName; + } + + public void setDiskOfferingDisplayText(String diskOfferingDisplayText) { + this.diskOfferingDisplayText = diskOfferingDisplayText; + } + + public String getDiskOfferingId() { + return diskOfferingId; + } + + public String getDiskOfferingName() { + return diskOfferingName; + } + + public String getDiskOfferingDisplayText() { + return diskOfferingDisplayText; + } } diff --git a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java index 8b2550151f3..f554cb5b58a 100644 --- a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java +++ b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java @@ -209,7 +209,7 @@ public class CheckNetworkAnswerTest { Long newSize = 4194304L; Long currentSize = 1048576L; - ResizeVolumeCommand rv = new ResizeVolumeCommand("dummydiskpath", new StorageFilerTO(dummypool), currentSize, newSize, false, "vmName"); + ResizeVolumeCommand rv = new ResizeVolumeCommand("dummydiskpath", new StorageFilerTO(dummypool), currentSize, newSize, false, "vmName", null); @Test public void testExecuteInSequence() { 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 c6b96bca3e6..733302f75e6 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 @@ -137,6 +137,8 @@ public interface VolumeOrchestrationService { StoragePool findStoragePool(DiskProfile dskCh, DataCenter dc, Pod pod, Long clusterId, Long hostId, VirtualMachine vm, Set avoid); + List findStoragePoolsForVolumeWithNewDiskOffering(DiskProfile dskCh, DataCenter dc, Pod pod, Long clusterId, Long hostId, VirtualMachine vm, Set avoid); + void updateVolumeDiskChain(long volumeId, String path, String chainInfo, String updatedDataStoreUUID); /** 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 82c3dd14cf3..3ffa496b544 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 @@ -67,14 +67,15 @@ 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("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, - @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap) throws InsufficientCapacityException; + @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId, @QueryParam("root-disk-offering-id") Long rootDiskOfferingId) throws InsufficientCapacityException; @POST VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId, @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, @QueryParam("os") String os, @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, @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) throws InsufficientCapacityException; + @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, + @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId) throws InsufficientCapacityException; @POST NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway); diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index a62876aeff0..23abb67dfa0 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -228,9 +228,9 @@ public interface StorageManager extends StorageService { HypervisorType getHypervisorTypeFromFormat(ImageFormat format); - boolean storagePoolHasEnoughIops(List volume, StoragePool pool); + boolean storagePoolHasEnoughIops(List> volumeDiskProfilePairs, StoragePool pool); - boolean storagePoolHasEnoughSpace(List volume, StoragePool pool); + boolean storagePoolHasEnoughSpace(List> volumeDiskProfilePairs, StoragePool pool); /** * This comment is relevant to managed storage only. @@ -254,13 +254,13 @@ public interface StorageManager extends StorageService { * * Cloning volumes on the back-end instead of copying down a new template for each new volume helps to alleviate load on the hypervisors. */ - boolean storagePoolHasEnoughSpace(List volume, StoragePool pool, Long clusterId); + boolean storagePoolHasEnoughSpace(List> volume, StoragePool pool, Long clusterId); boolean storagePoolHasEnoughSpaceForResize(StoragePool pool, long currentSize, long newSize); boolean storagePoolCompatibleWithVolumePool(StoragePool pool, Volume volume); - boolean isStoragePoolCompliantWithStoragePolicy(List volumes, StoragePool pool) throws StorageUnavailableException; + boolean isStoragePoolCompliantWithStoragePolicy(List> volumes, StoragePool pool) throws StorageUnavailableException; boolean registerHostListener(String providerUuid, HypervisorHostListener listener); 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 aeb4a2e2ba5..2cbcf6fb9d1 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -523,7 +523,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Override public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType) throws InsufficientCapacityException { - allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(serviceOffering), new ArrayList<>(), networks, plan, hyperType, null, null); + DiskOffering diskOffering = _diskOfferingDao.findById(serviceOffering.getId()); + allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), networks, plan, hyperType, null, null); } private VirtualMachineGuru getVmGuru(final VirtualMachine vm) { @@ -3738,8 +3739,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } final ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); + final DiskOfferingVO currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentServiceOffering.getDiskOfferingId()); + final DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newServiceOffering.getDiskOfferingId()); - checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newServiceOffering); + checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newDiskOffering); if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) { throw new InvalidParameterValueException("isSystem property is different for current service offering and new service offering"); @@ -3750,8 +3753,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac newServiceOffering.getCpu() + " cpu(s) at " + newServiceOffering.getSpeed() + " Mhz, and " + newServiceOffering.getRamSize() + " MB of memory"); } - final List currentTags = StringUtils.csvTagsToList(currentServiceOffering.getTags()); - final List newTags = StringUtils.csvTagsToList(newServiceOffering.getTags()); + final List currentTags = StringUtils.csvTagsToList(currentDiskOffering.getTags()); + final List newTags = StringUtils.csvTagsToList(newDiskOffering.getTags()); if (!newTags.containsAll(currentTags)) { throw new InvalidParameterValueException("Unable to upgrade virtual machine; the current service offering " + " should have tags as subset of " + "the new service offering tags. Current service offering tags: " + currentTags + "; " + "new service " + "offering tags: " + newTags); @@ -3761,16 +3764,16 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac /** * Throws an InvalidParameterValueException in case the new service offerings does not match the storage scope (e.g. local or shared). */ - protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, ServiceOffering newServiceOffering) { + protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, DiskOffering newDiskOffering) { boolean isRootVolumeOnLocalStorage = isRootVolumeOnLocalStorage(vmInstance.getId()); - if (newServiceOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) { + if (newDiskOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) { String message = String .format("Unable to upgrade virtual machine %s, target offering use local storage but the storage pool where " + "the volume is allocated is a shared storage.", vmInstance.toString()); throw new InvalidParameterValueException(message); } - if (!newServiceOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) { + if (!newDiskOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) { String message = String.format("Unable to upgrade virtual machine %s, target offering use shared storage but the storage pool where " + "the volume is allocated is a local storage.", vmInstance.toString()); throw new InvalidParameterValueException(message); 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 c6f8e418642..d639b4513e4 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 @@ -156,7 +156,7 @@ public class CloudOrchestrator implements OrchestrationService { @Override 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) throws InsufficientCapacityException { + Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, // vmEntityManager); @@ -185,10 +185,14 @@ public class CloudOrchestrator implements OrchestrationService { ServiceOfferingVO computeOffering = _serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); - rootDiskOfferingInfo.setDiskOffering(computeOffering); + DiskOfferingVO rootDiskOffering = _diskOfferingDao.findById(rootDiskOfferingId); + if (rootDiskOffering == null) { + throw new InvalidParameterValueException("Unable to find root disk offering " + rootDiskOfferingId); + } + rootDiskOfferingInfo.setDiskOffering(rootDiskOffering); rootDiskOfferingInfo.setSize(rootDiskSize); - if (computeOffering.isCustomizedIops() != null && computeOffering.isCustomizedIops()) { + if (rootDiskOffering.isCustomizedIops() != null && rootDiskOffering.isCustomizedIops()) { Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); if (userVmDetails != null) { @@ -200,48 +204,50 @@ public class CloudOrchestrator implements OrchestrationService { } } - if (vm.getDiskOfferingId() != null) { - DiskOfferingVO diskOffering = _diskOfferingDao.findById(vm.getDiskOfferingId()); + if (dataDiskOfferingId != null) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(dataDiskOfferingId); if (diskOffering == null) { - throw new InvalidParameterValueException("Unable to find disk offering " + vm.getDiskOfferingId()); + throw new InvalidParameterValueException("Unable to find data disk offering " + dataDiskOfferingId); } - Long size = null; - if (diskOffering.getDiskSize() == 0) { - size = diskSize; - if (size == null) { - throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); + if (!diskOffering.isComputeOnly()) { + Long size = null; + if (diskOffering.getDiskSize() == 0) { + size = diskSize; + if (size == null) { + throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); + } + _volumeMgr.validateVolumeSizeRange(size * 1024 * 1024 * 1024); } - _volumeMgr.validateVolumeSizeRange(size * 1024 * 1024 * 1024); - } - DiskOfferingInfo dataDiskOfferingInfo = new DiskOfferingInfo(); + DiskOfferingInfo dataDiskOfferingInfo = new DiskOfferingInfo(); - dataDiskOfferingInfo.setDiskOffering(diskOffering); - dataDiskOfferingInfo.setSize(size); + dataDiskOfferingInfo.setDiskOffering(diskOffering); + dataDiskOfferingInfo.setSize(size); - if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { - Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); + if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { + Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); - if (userVmDetails != null) { - String minIops = userVmDetails.get("minIopsDo"); - String maxIops = userVmDetails.get("maxIopsDo"); + if (userVmDetails != null) { + String minIops = userVmDetails.get("minIopsDo"); + String maxIops = userVmDetails.get("maxIopsDo"); - dataDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null); - dataDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null); + dataDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null); + dataDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null); + } } - } - dataDiskOfferings.add(dataDiskOfferingInfo); + dataDiskOfferings.add(dataDiskOfferingInfo); + } } if (dataDiskTemplateToDiskOfferingMap != null && !dataDiskTemplateToDiskOfferingMap.isEmpty()) { for (Entry datadiskTemplateToDiskOffering : dataDiskTemplateToDiskOfferingMap.entrySet()) { - DiskOffering diskOffering = datadiskTemplateToDiskOffering.getValue(); - if (diskOffering == null) { - throw new InvalidParameterValueException("Unable to find disk offering " + vm.getDiskOfferingId()); + DiskOffering dataDiskOffering = datadiskTemplateToDiskOffering.getValue(); + if (dataDiskOffering == null) { + throw new InvalidParameterValueException("Unable to find disk offering " + dataDiskOfferingId); } - if (diskOffering.getDiskSize() == 0) { // Custom disk offering is not supported for volumes created from datadisk templates - throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); + if (dataDiskOffering.getDiskSize() == 0) { // Custom disk offering is not supported for volumes created from datadisk templates + throw new InvalidParameterValueException("Disk offering " + dataDiskOffering + " requires size parameter."); } } } @@ -254,7 +260,8 @@ 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) + int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, + Map> extraDhcpOptionMap, Long diskOfferingId) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager); @@ -268,9 +275,6 @@ public class CloudOrchestrator implements OrchestrationService { DiskOfferingInfo rootDiskOfferingInfo = new DiskOfferingInfo(); - rootDiskOfferingInfo.setDiskOffering(computeOffering); - - Long diskOfferingId = vm.getDiskOfferingId(); if (diskOfferingId == null) { throw new InvalidParameterValueException("Installing from ISO requires a disk offering to be specified for the root disk."); } @@ -278,8 +282,10 @@ public class CloudOrchestrator implements OrchestrationService { if (diskOffering == null) { throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingId); } + rootDiskOfferingInfo.setDiskOffering(diskOffering); + Long size = null; - if (diskOffering.getDiskSize() == 0) { + if (!diskOffering.isComputeOnly() && diskOffering.getDiskSize() == 0) { size = diskSize; if (size == null) { throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); 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 4a30ee4043b..339153268fc 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 @@ -365,6 +365,36 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati return null; } + public List findStoragePoolsForVolumeWithNewDiskOffering(DiskProfile dskCh, DataCenter dc, Pod pod, Long clusterId, Long hostId, VirtualMachine vm, final Set avoid) { + Long podId = null; + if (pod != null) { + podId = pod.getId(); + } else if (clusterId != null) { + Cluster cluster = _entityMgr.findById(Cluster.class, clusterId); + if (cluster != null) { + podId = cluster.getPodId(); + } + } + + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + List suitablePools = new ArrayList<>(); + for (StoragePoolAllocator allocator : _storagePoolAllocators) { + + ExcludeList avoidList = new ExcludeList(); + for (StoragePool pool : avoid) { + avoidList.addPool(pool.getId()); + } + DataCenterDeployment plan = new DataCenterDeployment(dc.getId(), podId, clusterId, hostId, null, null); + + final List poolList = allocator.allocateToPool(dskCh, profile, plan, avoidList, StoragePoolAllocator.RETURN_UPTO_ALL); + if (CollectionUtils.isEmpty(poolList)) { + continue; + } + suitablePools.addAll(poolList); + } + return suitablePools; + } + @Override public StoragePool findChildDataStoreInDataStoreCluster(DataCenter dc, Pod pod, Long clusterId, Long hostId, VirtualMachine vm, Long datastoreClusterId) { Long podId = null; @@ -634,7 +664,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati DiskProfile dskCh = null; if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) { - dskCh = createDiskCharacteristics(volume, template, dc, offering); + dskCh = createDiskCharacteristics(volume, template, dc, diskOffering); storageMgr.setDiskProfileThrottling(dskCh, offering, diskOffering); } else { dskCh = createDiskCharacteristics(volume, template, dc, diskOffering); @@ -893,7 +923,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati Long offeringId = null; - if (offering.getType() == DiskOffering.Type.Disk) { + if (!offering.isComputeOnly()) { offeringId = offering.getId(); } diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index e478290d486..1655bc4634e 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -95,6 +95,9 @@ public class VirtualMachineManagerImplTest { @Mock private ServiceOfferingVO serviceOfferingMock; + @Mock + private DiskOfferingVO diskOfferingMock; + private long hostMockId = 1L; @Mock private HostVO hostMock; @@ -218,14 +221,15 @@ public class VirtualMachineManagerImplTest { when(serviceOfferingMock.getId()).thenReturn(2l); ServiceOfferingVO mockCurrentServiceOffering = mock(ServiceOfferingVO.class); + DiskOfferingVO mockCurrentDiskOffering = mock(DiskOfferingVO.class); when(serviceOfferingDaoMock.findByIdIncludingRemoved(anyLong(), anyLong())).thenReturn(mockCurrentServiceOffering); - when(mockCurrentServiceOffering.isUseLocalStorage()).thenReturn(true); - when(serviceOfferingMock.isUseLocalStorage()).thenReturn(true); + when(mockCurrentDiskOffering.isUseLocalStorage()).thenReturn(true); + when(diskOfferingMock.isUseLocalStorage()).thenReturn(true); when(mockCurrentServiceOffering.isSystemUse()).thenReturn(true); when(serviceOfferingMock.isSystemUse()).thenReturn(true); - when(mockCurrentServiceOffering.getTags()).thenReturn("x,y"); - when(serviceOfferingMock.getTags()).thenReturn("z,x,y"); + when(mockCurrentDiskOffering.getTags()).thenReturn("x,y"); + when(diskOfferingMock.getTags()).thenReturn("z,x,y"); virtualMachineManagerImpl.checkIfCanUpgrade(vmInstanceMock, serviceOfferingMock); } @@ -677,7 +681,7 @@ public class VirtualMachineManagerImplTest { private void prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(boolean isRootOnLocal, boolean isOfferingUsingLocal) { Mockito.doReturn(isRootOnLocal).when(virtualMachineManagerImpl).isRootVolumeOnLocalStorage(Mockito.anyLong()); Mockito.doReturn("vmInstanceMockedToString").when(vmInstanceMock).toString(); - Mockito.doReturn(isOfferingUsingLocal).when(serviceOfferingMock).isUseLocalStorage(); - virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, serviceOfferingMock); + Mockito.doReturn(isOfferingUsingLocal).when(diskOfferingMock).isUseLocalStorage(); + virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, diskOfferingMock); } } diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index a2362316420..6e309376924 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -16,25 +16,66 @@ // under the License. package com.cloud.service; +import java.util.Date; import java.util.Map; +import java.util.UUID; import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; -import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; import javax.persistence.Transient; +import javax.persistence.Id; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Enumerated; +import javax.persistence.EnumType; import com.cloud.offering.ServiceOffering; -import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.Storage.ProvisioningType; +import com.cloud.utils.db.GenericDao; import com.cloud.vm.VirtualMachine; @Entity @Table(name = "service_offering") -@DiscriminatorValue(value = "Service") -@PrimaryKeyJoinColumn(name = "id") -public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering { +public class ServiceOfferingVO implements ServiceOffering { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name = null; + + @Column(name = "unique_name") + private String uniqueName; + + @Column(name = "display_text", length = 4096) + private String displayText = null; + + @Column(name = "customized") + private boolean customized; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(TemporalType.TIMESTAMP) + private Date removed; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Enumerated(EnumType.STRING) + @Column(name = "state") + ServiceOffering.State state = ServiceOffering.State.Active; + + @Column(name = "disk_offering_id") + private Long diskOfferingId; + + @Column(name = "disk_offering_strictness") + private boolean diskOfferingStrictness = false; @Column(name = "cpu") private Integer cpu; @@ -75,6 +116,9 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering @Column(name = "deployment_planner") private String deploymentPlanner = null; + @Column(name = "system_use") + private boolean systemUse; + @Column(name = "dynamic_scaling_enabled") private boolean dynamicScalingEnabled = true; @@ -94,8 +138,7 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering } public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, String displayText, - ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType, boolean defaultUse) { - super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true); + boolean systemUse, VirtualMachine.Type vmType, boolean defaultUse) { this.cpu = cpu; this.ramSize = ramSize; this.speed = speed; @@ -106,12 +149,15 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering volatileVm = false; this.defaultUse = defaultUse; this.vmType = vmType == null ? null : vmType.toString().toLowerCase(); + uuid = UUID.randomUUID().toString(); + this.systemUse = systemUse; + this.name = name; + this.displayText = displayText; } public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, - boolean limitResourceUse, boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, - VirtualMachine.Type vmType, String hostTag, String deploymentPlanner, boolean dynamicScalingEnabled) { - super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true); + boolean limitResourceUse, boolean volatileVm, String displayText, boolean systemUse, + VirtualMachine.Type vmType, String hostTag, String deploymentPlanner, boolean dynamicScalingEnabled, boolean isCustomized) { this.cpu = cpu; this.ramSize = ramSize; this.speed = speed; @@ -123,23 +169,20 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering this.vmType = vmType == null ? null : vmType.toString().toLowerCase(); this.hostTag = hostTag; this.deploymentPlanner = deploymentPlanner; + uuid = UUID.randomUUID().toString(); + this.systemUse = systemUse; + this.name = name; + this.displayText = displayText; this.dynamicScalingEnabled = dynamicScalingEnabled; + this.customized = isCustomized; } public ServiceOfferingVO(ServiceOfferingVO offering) { - super(offering.getId(), - offering.getName(), - offering.getDisplayText(), - offering.getProvisioningType(), - false, - offering.getTags(), - offering.isRecreatable(), - offering.isUseLocalStorage(), - offering.isSystemUse(), - true, - offering.isCustomizedIops()== null ? false:offering.isCustomizedIops(), - offering.getMinIops(), - offering.getMaxIops()); + id = offering.getId(); + diskOfferingId = offering.getDiskOfferingId(); + name = offering.getName(); + displayText = offering.getDisplayText(); + customized = true; cpu = offering.getCpu(); ramSize = offering.getRamSize(); speed = offering.getSpeed(); @@ -150,6 +193,7 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering volatileVm = offering.isVolatileVm(); hostTag = offering.getHostTag(); vmType = offering.getSystemVmType(); + systemUse = offering.isSystemUse(); dynamicScalingEnabled = offering.isDynamicScalingEnabled(); } @@ -176,17 +220,6 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering return defaultUse; } - @Override - @Transient - public String[] getTagsArray() { - String tags = getTags(); - if (tags == null || tags.length() == 0) { - return new String[0]; - } - - return tags.split(","); - } - @Override public Integer getCpu() { return cpu; @@ -297,6 +330,98 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering return isCustomized() && getDetail("minCPU") != null; } + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isSystemUse() { + return systemUse; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getUniqueName() { + return uniqueName; + } + + @Override + public void setUniqueName(String uniqueName) { + this.uniqueName = uniqueName; + } + + @Override + public String getDisplayText() { + return displayText; + } + + @Override + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + @Override + public boolean isCustomized() { + return customized; + } + + @Override + public void setCustomized(boolean customized) { + this.customized = customized; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public ServiceOffering.State getState() { + return state; + } + + @Override + public void setState(ServiceOffering.State state) { + this.state = state; + } + + @Override + public Long getDiskOfferingId() { + return diskOfferingId; + } + + public void setDiskOfferingId(Long diskOfferingId) { + this.diskOfferingId = diskOfferingId; + } + + @Override + public String getUuid() { + return uuid; + } + @Override public String toString() { return String.format("Service offering {\"id\": %s, \"name\": \"%s\", \"uuid\": \"%s\"}", getId(), getName(), getUuid()); @@ -309,4 +434,12 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public void setDynamicScalingEnabled(boolean dynamicScalingEnabled) { this.dynamicScalingEnabled = dynamicScalingEnabled; } + + public Boolean getDiskOfferingStrictness() { + return diskOfferingStrictness; + } + + public void setDiskOfferingStrictness(boolean diskOfferingStrictness) { + this.diskOfferingStrictness = diskOfferingStrictness; + } } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java index 623179c2d8e..e2fc5b49ae8 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java @@ -53,4 +53,6 @@ public interface ServiceOfferingDao extends GenericDao ServiceOfferingVO findDefaultSystemOffering(String offeringName, Boolean useLocalStorage); List listPublicByCpuAndMemory(Integer cpus, Integer memory); + + ServiceOfferingVO findServiceOfferingByComputeOnlyDiskOffering(long diskOfferingId); } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java index 14400516d54..5c8e4993829 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -24,6 +24,8 @@ import java.util.Map; import javax.inject.Inject; import javax.persistence.EntityExistsException; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.dao.DiskOfferingDao; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -48,17 +50,20 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase UniqueNameSearch; protected final SearchBuilder ServiceOfferingsByKeywordSearch; protected final SearchBuilder PublicCpuRamSearch; + protected final SearchBuilder SearchComputeOfferingByComputeOnlyDiskOffering; public ServiceOfferingDaoImpl() { super(); UniqueNameSearch = createSearchBuilder(); UniqueNameSearch.and("name", UniqueNameSearch.entity().getUniqueName(), SearchCriteria.Op.EQ); - UniqueNameSearch.and("system", UniqueNameSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); + UniqueNameSearch.and("system_use", UniqueNameSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); UniqueNameSearch.done(); ServiceOfferingsByKeywordSearch = createSearchBuilder(); @@ -71,13 +76,17 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase sc = UniqueNameSearch.create(); sc.setParameters("name", name); - sc.setParameters("system", true); + sc.setParameters("system_use", true); List vos = search(sc, null, null, false); if (vos.size() == 0) { return null; @@ -92,13 +101,14 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase createSystemServiceOfferings(String name, String uniqueName, int cpuCount, int ramSize, int cpuSpeed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, String displayText, ProvisioningType provisioningType, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType, boolean defaultUse) { + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(name, displayText, provisioningType, false, tags, recreatable, false, true); + diskOfferingVO.setUniqueName(uniqueName); + diskOfferingVO = diskOfferingDao.persistDefaultDiskOffering(diskOfferingVO); + List list = new ArrayList(); ServiceOfferingVO offering = new ServiceOfferingVO(name, cpuCount, ramSize, cpuSpeed, rateMbps, multicastRateMbps, offerHA, displayText, - provisioningType, false, recreatable, tags, systemUse, vmType, defaultUse); + systemUse, vmType, defaultUse); offering.setUniqueName(uniqueName); + offering.setDiskOfferingId(diskOfferingVO.getId()); offering = persistSystemServiceOffering(offering); if (offering != null) { list.add(offering); } boolean useLocal = true; - if (offering.isUseLocalStorage()) { // if 1st one is already local then 2nd needs to be shared + if (diskOfferingVO.isUseLocalStorage()) { // if 1st one is already local then 2nd needs to be shared useLocal = false; } + diskOfferingVO = new DiskOfferingVO(name + (useLocal ? " - Local Storage" : ""), displayText, provisioningType, false, tags, recreatable, useLocal, true); + diskOfferingVO.setUniqueName(uniqueName + (useLocal ? "-Local" : "")); + diskOfferingVO = diskOfferingDao.persistDefaultDiskOffering(diskOfferingVO); + offering = new ServiceOfferingVO(name + (useLocal ? " - Local Storage" : ""), cpuCount, ramSize, cpuSpeed, rateMbps, multicastRateMbps, offerHA, displayText, - provisioningType, useLocal, recreatable, tags, systemUse, vmType, defaultUse); + systemUse, vmType, defaultUse); offering.setUniqueName(uniqueName + (useLocal ? "-Local" : "")); + offering.setDiskOfferingId(diskOfferingVO.getId()); offering = persistSystemServiceOffering(offering); if (offering != null) { list.add(offering); @@ -262,4 +282,15 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase sc = SearchComputeOfferingByComputeOnlyDiskOffering.create(); + sc.setParameters("disk_offering_id", diskOfferingId); + List vos = listBy(sc); + if (vos.size() == 0) { + return null; + } + return vos.get(0); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java index 952d3014209..bfdbba2a6d3 100644 --- a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java @@ -21,16 +21,12 @@ import java.util.List; import java.util.UUID; import javax.persistence.Column; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; 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.Inheritance; -import javax.persistence.InheritanceType; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -41,8 +37,6 @@ import com.cloud.utils.db.GenericDao; @Entity @Table(name = "disk_offering") -@Inheritance(strategy = InheritanceType.JOINED) -@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING, length = 32) public class DiskOfferingVO implements DiskOffering { @Id @@ -65,8 +59,8 @@ public class DiskOfferingVO implements DiskOffering { @Column(name = "tags", length = 4096) String tags; - @Column(name = "type") - Type type; + @Column(name = "compute_only") + boolean computeOnly; @Column(name = GenericDao.REMOVED_COLUMN) @Temporal(TemporalType.TIMESTAMP) @@ -81,9 +75,6 @@ public class DiskOfferingVO implements DiskOffering { @Column(name = "use_local_storage") private boolean useLocalStorage; - @Column(name = "system_use") - private boolean systemUse; - @Column(name = "customized") private boolean customized; @@ -156,6 +147,9 @@ public class DiskOfferingVO implements DiskOffering { @Column(name = "hv_ss_reserve") Integer hypervisorSnapshotReserve; + @Column(name = "disk_size_strictness") + boolean diskSizeStrictness = false; + public DiskOfferingVO() { uuid = UUID.randomUUID().toString(); } @@ -168,7 +162,7 @@ public class DiskOfferingVO implements DiskOffering { this.diskSize = diskSize; this.tags = tags; recreatable = false; - type = Type.Disk; + computeOnly = false; useLocalStorage = false; customized = isCustomized; uuid = UUID.randomUUID().toString(); @@ -186,7 +180,7 @@ public class DiskOfferingVO implements DiskOffering { this.diskSize = diskSize; this.tags = tags; recreatable = false; - type = Type.Disk; + computeOnly = false; useLocalStorage = false; customized = isCustomized; uuid = UUID.randomUUID().toString(); @@ -196,32 +190,30 @@ public class DiskOfferingVO implements DiskOffering { state = State.Active; } - public DiskOfferingVO(String name, String displayText, Storage.ProvisioningType provisioningType, boolean mirrored, String tags, boolean recreatable, boolean useLocalStorage, boolean systemUse, + public DiskOfferingVO(String name, String displayText, Storage.ProvisioningType provisioningType, boolean mirrored, String tags, boolean recreatable, boolean useLocalStorage, boolean customized) { - type = Type.Service; + computeOnly = true; this.name = name; this.displayText = displayText; this.provisioningType = provisioningType; this.tags = tags; this.recreatable = recreatable; this.useLocalStorage = useLocalStorage; - this.systemUse = systemUse; this.customized = customized; uuid = UUID.randomUUID().toString(); state = State.Active; } public DiskOfferingVO(long id, String name, String displayText, Storage.ProvisioningType provisioningType, boolean mirrored, String tags, boolean recreatable, boolean useLocalStorage, - boolean systemUse, boolean customized, boolean customizedIops, Long minIops, Long maxIops) { + boolean customized, boolean customizedIops, Long minIops, Long maxIops) { this.id = id; - type = Type.Service; + computeOnly = true; this.name = name; this.displayText = displayText; this.provisioningType = provisioningType; this.tags = tags; this.recreatable = recreatable; this.useLocalStorage = useLocalStorage; - this.systemUse = systemUse; this.customized = customized; this.customizedIops = customizedIops; uuid = UUID.randomUUID().toString(); @@ -304,8 +296,8 @@ public class DiskOfferingVO implements DiskOffering { } @Override - public Type getType() { - return type; + public boolean isComputeOnly() { + return computeOnly; } @Override @@ -322,15 +314,6 @@ public class DiskOfferingVO implements DiskOffering { this.name = name; } - @Override - public boolean isSystemUse() { - return systemUse; - } - - public void setSystemUse(boolean systemUse) { - this.systemUse = systemUse; - } - @Override public String getDisplayText() { return displayText; @@ -588,4 +571,12 @@ public class DiskOfferingVO implements DiskOffering { public boolean isShared() { return !useLocalStorage; } + + public boolean getDiskSizeStrictness() { + return diskSizeStrictness; + } + + public void setDiskSizeStrictness(boolean diskSizeStrictness) { + this.diskSizeStrictness = diskSizeStrictness; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java index 3305752459d..b0889257121 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java @@ -24,13 +24,9 @@ import com.cloud.utils.db.GenericDao; public interface DiskOfferingDao extends GenericDao { - List findPrivateDiskOffering(); - - List findPublicDiskOfferings(); - DiskOfferingVO findByUniqueName(String uniqueName); - DiskOfferingVO persistDeafultDiskOffering(DiskOfferingVO offering); + DiskOfferingVO persistDefaultDiskOffering(DiskOfferingVO offering); List listAllBySizeAndProvisioningType(long size, Storage.ProvisioningType provisioningType); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java index b9fa10c1236..435474ed942 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java @@ -29,7 +29,6 @@ import javax.persistence.EntityExistsException; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.springframework.stereotype.Component; -import com.cloud.offering.DiskOffering.Type; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.Storage; import com.cloud.utils.db.Attribute; @@ -37,7 +36,6 @@ 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 com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; @@ -47,48 +45,28 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im @Inject protected DiskOfferingDetailsDao detailsDao; - private final SearchBuilder PrivateDiskOfferingSearch; - private final SearchBuilder PublicDiskOfferingSearch; protected final SearchBuilder UniqueNameSearch; private final String SizeDiskOfferingSearch = "SELECT * FROM disk_offering WHERE " + "disk_size = ? AND provisioning_type = ? AND removed IS NULL"; - private final Attribute _typeAttr; + private final Attribute _computeOnlyAttr; protected final static long GB_UNIT_BYTES = 1024 * 1024 * 1024; protected DiskOfferingDaoImpl() { - PrivateDiskOfferingSearch = createSearchBuilder(); - PrivateDiskOfferingSearch.and("diskSize", PrivateDiskOfferingSearch.entity().getDiskSize(), SearchCriteria.Op.EQ); - PrivateDiskOfferingSearch.done(); - - PublicDiskOfferingSearch = createSearchBuilder(); - PublicDiskOfferingSearch.and("system", PublicDiskOfferingSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); - PublicDiskOfferingSearch.and("removed", PublicDiskOfferingSearch.entity().getRemoved(), SearchCriteria.Op.NULL); - PublicDiskOfferingSearch.done(); - UniqueNameSearch = createSearchBuilder(); UniqueNameSearch.and("name", UniqueNameSearch.entity().getUniqueName(), SearchCriteria.Op.EQ); UniqueNameSearch.done(); - _typeAttr = _allAttributes.get("type"); - } - - @Override - public List findPrivateDiskOffering() { - SearchCriteria sc = PrivateDiskOfferingSearch.create(); - sc.setParameters("diskSize", 0); - return listBy(sc); + _computeOnlyAttr = _allAttributes.get("computeOnly"); } @Override public List searchIncludingRemoved(SearchCriteria sc, final Filter filter, final Boolean lock, final boolean cache) { - sc.addAnd(_typeAttr, Op.EQ, Type.Disk); return super.searchIncludingRemoved(sc, filter, lock, cache); } @Override public List customSearchIncludingRemoved(SearchCriteria sc, final Filter filter) { - sc.addAnd(_typeAttr, Op.EQ, Type.Disk); return super.customSearchIncludingRemoved(sc, filter); } @@ -97,23 +75,12 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im StringBuilder builder = new StringBuilder(sql); int index = builder.indexOf("WHERE"); if (index == -1) { - builder.append(" WHERE type=?"); + builder.append(" WHERE compute_only=?"); } else { - builder.insert(index + 6, "type=? "); + builder.insert(index + 6, "compute_only=? "); } - return super.executeList(sql, Type.Disk, params); - } - - @Override - public List findPublicDiskOfferings() { - SearchCriteria sc = PublicDiskOfferingSearch.create(); - sc.setParameters("system", false); - List offerings = listBy(sc); - if(offerings!=null) { - offerings.removeIf(o -> (!detailsDao.findDomainIds(o.getId()).isEmpty())); - } - return offerings; + return super.executeList(sql, false, params); } @Override @@ -129,7 +96,7 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im } @Override - public DiskOfferingVO persistDeafultDiskOffering(DiskOfferingVO offering) { + public DiskOfferingVO persistDefaultDiskOffering(DiskOfferingVO offering) { assert offering.getUniqueName() != null : "unique name shouldn't be null for the disk offering"; DiskOfferingVO vo = findByUniqueName(offering.getUniqueName()); if (vo != null) { diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java index e62162e2596..dad6d10e376 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java @@ -73,8 +73,8 @@ public class UserVmVO extends VMInstanceVO implements UserVm { } public UserVmVO(long id, String instanceName, String displayName, long templateId, HypervisorType hypervisorType, long guestOsId, boolean haEnabled, - boolean limitCpuUse, long domainId, long accountId, long userId, long serviceOfferingId, String userData, String name, Long diskOfferingId) { - super(id, serviceOfferingId, name, instanceName, Type.User, templateId, hypervisorType, guestOsId, domainId, accountId, userId, haEnabled, limitCpuUse, diskOfferingId); + boolean limitCpuUse, long domainId, long accountId, long userId, long serviceOfferingId, String userData, String name) { + super(id, serviceOfferingId, name, instanceName, Type.User, templateId, hypervisorType, guestOsId, domainId, accountId, userId, haEnabled, limitCpuUse); this.userData = userData; this.displayName = displayName; this.details = new HashMap(); diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index b1debd0b894..e0b37e47628 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -174,9 +174,6 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject networkIds; - @Column(name = "disk_offering_id") - protected Long diskOfferingId; - @Column(name = "display_vm", updatable = true, nullable = false) protected boolean display = true; @@ -196,7 +193,7 @@ public class VMEntityVO implements VirtualMachine, FiniteStateObject details = new HashMap<>(); diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java index d60e623661d..b7d565f3df2 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java @@ -338,7 +338,7 @@ public class DefaultVMSnapshotStrategy extends ManagerBase implements VMSnapshot Long offeringId = null; if (diskOfferingId != null) { DiskOfferingVO offering = diskOfferingDao.findById(diskOfferingId); - if (offering != null && (offering.getType() == DiskOfferingVO.Type.Disk)) { + if (offering != null && !offering.isComputeOnly()) { offeringId = offering.getId(); } } diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java index a124a4adf2b..34ea4908b7e 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java @@ -456,7 +456,7 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot Long offeringId = null; if (diskOfferingId != null) { DiskOfferingVO offering = diskOfferingDao.findById(diskOfferingId); - if (offering != null && (offering.getType() == DiskOfferingVO.Type.Disk)) { + if (offering != null && !offering.isComputeOnly()) { offeringId = offering.getId(); } } diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java index af206a7378e..41a83255584 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java @@ -33,6 +33,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.log4j.Logger; +import com.cloud.utils.Pair; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -265,8 +266,8 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement } // check capacity - List requestVolumes = new ArrayList<>(); - requestVolumes.add(volume); + List> requestVolumeDiskProfilePairs = new ArrayList<>(); + requestVolumeDiskProfilePairs.add(new Pair<>(volume, dskCh)); if (dskCh.getHypervisorType() == HypervisorType.VMware) { // Skip the parent datastore cluster, consider only child storage pools in it if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster && storageMgr.isStoragePoolDatastoreClusterParent(pool)) { @@ -281,7 +282,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement } try { - boolean isStoragePoolStoragepolicyComplaince = storageMgr.isStoragePoolCompliantWithStoragePolicy(requestVolumes, pool); + boolean isStoragePoolStoragepolicyComplaince = storageMgr.isStoragePoolCompliantWithStoragePolicy(requestVolumeDiskProfilePairs, pool); if (!isStoragePoolStoragepolicyComplaince) { return false; } @@ -290,7 +291,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement return false; } } - return storageMgr.storagePoolHasEnoughIops(requestVolumes, pool) && storageMgr.storagePoolHasEnoughSpace(requestVolumes, pool, plan.getClusterId()); + return storageMgr.storagePoolHasEnoughIops(requestVolumeDiskProfilePairs, pool) && storageMgr.storagePoolHasEnoughSpace(requestVolumeDiskProfilePairs, pool, plan.getClusterId()); } private boolean checkDiskProvisioningSupport(DiskProfile dskCh, StoragePool pool) { diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java index ab6b9311c69..0602f2dc782 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java @@ -16,26 +16,66 @@ //under the License. package org.apache.cloudstack.quota.vo; +import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; -import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; +import javax.persistence.Id; +import javax.persistence.GenerationType; +import javax.persistence.GeneratedValue; +import javax.persistence.TemporalType; +import javax.persistence.Temporal; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Transient; import com.cloud.offering.ServiceOffering; -import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.Storage.ProvisioningType; -import com.cloud.vm.VirtualMachine; +import com.cloud.utils.db.GenericDao; @Entity @Table(name = "service_offering") -@DiscriminatorValue(value = "Service") -@PrimaryKeyJoinColumn(name = "id") -public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering { +public class ServiceOfferingVO implements ServiceOffering { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name = null; + + @Column(name = "unique_name") + private String uniqueName; + + @Column(name = "display_text", length = 4096) + private String displayText = null; + + @Column(name = "customized") + private boolean customized; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(TemporalType.TIMESTAMP) + private Date removed; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Enumerated(EnumType.STRING) + @Column(name = "state") + ServiceOffering.State state = ServiceOffering.State.Active; + + @Column(name = "disk_offering_id") + private Long diskOfferingId; + + @Column(name = "disk_offering_strictness") + private boolean diskOfferingStrictness = false; + @Column(name = "cpu") private Integer cpu; @@ -78,6 +118,9 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering @Column(name = "dynamic_scaling_enabled") private boolean dynamicScalingEnabled; + @Column(name = "system_use") + private boolean systemUse; + @Transient Map details = new HashMap(); @@ -88,56 +131,12 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering super(); } - public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, String displayText, - ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType, boolean defaultUse) { - super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true); - this.cpu = cpu; - this.ramSize = ramSize; - this.speed = speed; - this.rateMbps = rateMbps; - this.multicastRateMbps = multicastRateMbps; - this.offerHA = offerHA; - limitCpuUse = false; - volatileVm = false; - this.defaultUse = defaultUse; - this.vmType = vmType == null ? null : vmType.toString().toLowerCase(); - } - - public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitCpuUse, - boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, - VirtualMachine.Type vmType, Long domainId) { - super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true); - this.cpu = cpu; - this.ramSize = ramSize; - this.speed = speed; - this.rateMbps = rateMbps; - this.multicastRateMbps = multicastRateMbps; - this.offerHA = offerHA; - this.limitCpuUse = limitCpuUse; - this.volatileVm = volatileVm; - this.vmType = vmType == null ? null : vmType.toString().toLowerCase(); - } - - public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitResourceUse, - boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, - VirtualMachine.Type vmType, Long domainId, String hostTag) { - this(name, cpu, ramSize, speed, rateMbps, multicastRateMbps, offerHA, limitResourceUse, volatileVm, displayText, provisioningType, useLocalStorage, recreatable, tags, - systemUse, vmType, domainId); - this.hostTag = hostTag; - } - - public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitResourceUse, - boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, - VirtualMachine.Type vmType, Long domainId, String hostTag, String deploymentPlanner) { - this(name, cpu, ramSize, speed, rateMbps, multicastRateMbps, offerHA, limitResourceUse, volatileVm, displayText, provisioningType, useLocalStorage, recreatable, tags, - systemUse, vmType, domainId, hostTag); - this.deploymentPlanner = deploymentPlanner; - } - public ServiceOfferingVO(ServiceOfferingVO offering) { - super(offering.getId(), offering.getName(), offering.getDisplayText(), offering.getProvisioningType(), false, offering.getTags(), offering.isRecreatable(), - offering.isUseLocalStorage(), offering.isSystemUse(), true, offering.isCustomizedIops() == null ? false : offering.isCustomizedIops(), - offering.getMinIops(), offering.getMaxIops()); + id = offering.getId(); + diskOfferingId = offering.getDiskOfferingId(); + name = offering.getName(); + displayText = offering.getDisplayText(); + customized = true; cpu = offering.getCpu(); ramSize = offering.getRamSize(); speed = offering.getSpeed(); @@ -148,6 +147,8 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering volatileVm = offering.isVolatileVm(); hostTag = offering.getHostTag(); vmType = offering.getSystemVmType(); + systemUse = offering.isSystemUse(); + dynamicScalingEnabled = offering.isDynamicScalingEnabled(); } @Override @@ -173,17 +174,6 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering return defaultUse; } - @Override - @Transient - public String[] getTagsArray() { - String tags = getTags(); - if (tags == null || tags.length() == 0) { - return new String[0]; - } - - return tags.split(","); - } - @Override public Integer getCpu() { return cpu; @@ -288,6 +278,108 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering isDynamic = isdynamic; } + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isSystemUse() { + return systemUse; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getUniqueName() { + return uniqueName; + } + + @Override + public void setUniqueName(String uniqueName) { + this.uniqueName = uniqueName; + } + + @Override + public String getDisplayText() { + return displayText; + } + + @Override + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + @Override + public boolean isCustomized() { + return customized; + } + + @Override + public void setCustomized(boolean customized) { + this.customized = customized; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public ServiceOffering.State getState() { + return state; + } + + @Override + public void setState(ServiceOffering.State state) { + this.state = state; + } + + @Override + public Long getDiskOfferingId() { + return diskOfferingId; + } + + @Override + public Boolean getDiskOfferingStrictness() { + return diskOfferingStrictness; + } + + @Override + public void setDiskOfferingStrictness(boolean diskOfferingStrictness) { + + } + + public void setDiskOfferingId(Long diskOfferingId) { + this.diskOfferingId = diskOfferingId; + } + + @Override + public String getUuid() { + return uuid; + } + @Override public boolean isDynamicScalingEnabled() { return dynamicScalingEnabled; diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index 4106cc05799..1f654588179 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -4836,7 +4836,7 @@ public class LibvirtComputingResourceTest { final boolean shrinkOk = true; final String vmInstance = "Test"; - final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance); + final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance, null); final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); @@ -4889,7 +4889,7 @@ public class LibvirtComputingResourceTest { final boolean shrinkOk = false; final String vmInstance = "Test"; - final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance); + final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance, null); final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); @@ -4929,7 +4929,7 @@ public class LibvirtComputingResourceTest { final boolean shrinkOk = false; final String vmInstance = "Test"; - final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance); + final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance, null); final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); assertNotNull(wrapper); @@ -4947,7 +4947,7 @@ public class LibvirtComputingResourceTest { final boolean shrinkOk = true; final String vmInstance = "Test"; - final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance); + final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance, null); final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); @@ -4976,7 +4976,7 @@ public class LibvirtComputingResourceTest { final boolean shrinkOk = false; final String vmInstance = "Test"; - final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance); + final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance, null); final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); @@ -5024,7 +5024,7 @@ public class LibvirtComputingResourceTest { final boolean shrinkOk = false; final String vmInstance = "Test"; - final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance); + final ResizeVolumeCommand command = new ResizeVolumeCommand(path, pool, currentSize, newSize, shrinkOk, vmInstance, null); final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index fa5c6423687..a5e4140357b 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -454,8 +454,13 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co */ private ServiceOfferingVO createServiceOfferingForVMImporting(Integer cpus, Integer memory, Integer maxCpuUsage) { String name = "Imported-" + cpus + "-" + memory; - ServiceOfferingVO vo = new ServiceOfferingVO(name, cpus, memory, maxCpuUsage, null, null, false, name, Storage.ProvisioningType.THIN, false, false, null, false, Type.User, + + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(name, name, Storage.ProvisioningType.THIN, false, null, false, false, true); + diskOfferingVO = diskOfferingDao.persistDefaultDiskOffering(diskOfferingVO); + + ServiceOfferingVO vo = new ServiceOfferingVO(name, cpus, memory, maxCpuUsage, null, null, false, name, false, Type.User, false); + vo.setDiskOfferingId(diskOfferingVO.getId()); return serviceOfferingDao.persist(vo); } @@ -665,7 +670,7 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co vmInternalName, id, vmInternalName, templateId, guestOsId, serviceOfferingId)); UserVmVO vmInstanceVO = new UserVmVO(id, vmInternalName, vmInternalName, templateId, HypervisorType.VMware, guestOsId, false, false, domainId, accountId, userId, - serviceOfferingId, null, vmInternalName, null); + serviceOfferingId, null, vmInternalName); vmInstanceVO.setDataCenterId(zoneId); return userVmDao.persist(vmInstanceVO); } diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java index 219c76a26f7..9d18e73afb4 100755 --- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java +++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java @@ -430,7 +430,7 @@ public class CitrixRequestWrapperTest { public void testResizeVolumeCommand() { final StorageFilerTO pool = Mockito.mock(StorageFilerTO.class); - final ResizeVolumeCommand resizeCommand = new ResizeVolumeCommand("Test", pool, 1l, 3l, false, "Tests-1"); + final ResizeVolumeCommand resizeCommand = new ResizeVolumeCommand("Test", pool, 1l, 3l, false, "Tests-1", null); final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance(); assertNotNull(wrapper); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 1ffc62efb1e..20be84dda44 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -380,7 +380,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu nodeVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, kubernetesCluster.getKeyPair(), - null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE); + null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); if (LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Created node VM : %s, %s in the Kubernetes cluster : %s", hostName, nodeVm.getUuid(), kubernetesCluster.getName())); } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java index dc34d37c38f..9bed248f7b5 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java @@ -209,7 +209,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif controlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, kubernetesCluster.getKeyPair(), - requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE); + requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); if (LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Created control VM ID: %s, %s in the Kubernetes cluster : %s", controlVm.getUuid(), hostName, kubernetesCluster.getName())); } @@ -264,7 +264,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif additionalControlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, kubernetesCluster.getKeyPair(), - null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE); + null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); if (LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Created control VM ID : %s, %s in the Kubernetes cluster : %s", additionalControlVm.getUuid(), hostName, kubernetesCluster.getName())); } diff --git a/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java b/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java index 103fcd9cf31..62fd2dd2845 100644 --- a/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java +++ b/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java @@ -58,7 +58,6 @@ import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.Storage; import com.cloud.storage.Storage.ProvisioningType; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; @@ -121,7 +120,7 @@ public class InternalLBVMManagerTest extends TestCase { //mock system offering creation as it's used by configure() method called by initComponentsLifeCycle Mockito.when(_accountMgr.getAccount(1L)).thenReturn(new AccountVO()); ServiceOfferingVO off = new ServiceOfferingVO("alena", 1, 1, - 1, 1, 1, false, "alena", Storage.ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.InternalLoadBalancerVm, false); + 1, 1, 1, false, "alena", false, VirtualMachine.Type.InternalLoadBalancerVm, false); off = setId(off, 1); List list = new ArrayList(); list.add(off); @@ -341,7 +340,7 @@ public class InternalLBVMManagerTest extends TestCase { final ServiceOfferingVO voToReturn = vo; final Class c = voToReturn.getClass(); try { - final Field f = c.getSuperclass().getDeclaredField("id"); + final Field f = c.getDeclaredField("id"); f.setAccessible(true); f.setLong(voToReturn, id); } catch (final NoSuchFieldException ex) { diff --git a/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java b/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java index 898b7e5121b..9141190df1f 100644 --- a/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java +++ b/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java @@ -45,7 +45,6 @@ import com.cloud.network.router.VirtualRouter; import com.cloud.network.router.VirtualRouter.Role; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.Storage; import com.cloud.storage.Storage.ProvisioningType; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; @@ -93,7 +92,7 @@ public class InternalLBVMServiceTest extends TestCase { //mock system offering creation as it's used by configure() method called by initComponentsLifeCycle Mockito.when(_accountMgr.getAccount(1L)).thenReturn(new AccountVO()); ServiceOfferingVO off = new ServiceOfferingVO("alena", 1, 1, - 1, 1, 1, false, "alena", Storage.ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.InternalLoadBalancerVm, false); + 1, 1, 1, false, "alena", false, VirtualMachine.Type.InternalLoadBalancerVm, false); off = setId(off, 1); List list = new ArrayList(); list.add(off); @@ -251,7 +250,7 @@ public class InternalLBVMServiceTest extends TestCase { final ServiceOfferingVO voToReturn = vo; final Class c = voToReturn.getClass(); try { - final Field f = c.getSuperclass().getDeclaredField("id"); + final Field f = c.getDeclaredField("id"); f.setAccessible(true); f.setLong(voToReturn, id); } catch (final NoSuchFieldException ex) { diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceVirtualMachine.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceVirtualMachine.java index 676afc9ae8a..b9a446b69a0 100644 --- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceVirtualMachine.java +++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceVirtualMachine.java @@ -23,6 +23,6 @@ import com.cloud.vm.UserVmVO; public class ServiceVirtualMachine extends UserVmVO { public ServiceVirtualMachine(long id, String instanceName, String name, long templateId, long serviceOfferingId, HypervisorType hypervisorType, long guestOSId, long dataCenterId, long domainId, long accountId, long userId, boolean haEnabled) { - super(id, instanceName, name, templateId, hypervisorType, guestOSId, false, false, domainId, accountId, userId, serviceOfferingId, null, name, null); + super(id, instanceName, name, templateId, hypervisorType, guestOSId, false, false, domainId, accountId, userId, serviceOfferingId, null, name); } } diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/ManagementServerMock.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/ManagementServerMock.java index 64a036a2c62..859330071e9 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/ManagementServerMock.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/ManagementServerMock.java @@ -240,7 +240,7 @@ public class ManagementServerMock { long id = _userVmDao.getNextInSequence(Long.class, "id"); UserVmVO vm = new UserVmVO(id, name, name, tmpl.getId(), HypervisorType.XenServer, tmpl.getGuestOSId(), false, false, _zone.getDomainId(), Account.ACCOUNT_ID_SYSTEM, - 1, small.getId(), null, name, null); + 1, small.getId(), null, name); vm.setState(com.cloud.vm.VirtualMachine.State.Running); vm.setHostId(_hostId); vm.setDataCenterId(network.getDataCenterId()); diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 19cf297b5ad..320860380ce 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -617,7 +617,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), oldSize, resizeParameter.newSize, resizeParameter.shrinkOk, - resizeParameter.instanceName); + resizeParameter.instanceName, null); CreateCmdResult result = new CreateCmdResult(null, null); try { ResizeVolumeAnswer answer = (ResizeVolumeAnswer) _storageMgr.sendToPool(pool, resizeParameter.hosts, resizeCmd); diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 7f4199348c9..e6413a61ba4 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -1043,14 +1043,26 @@ public class ApiDBUtils { return null; } - public static DiskOfferingVO findDiskOfferingById(Long diskOfferingId) { + public static DiskOfferingVO findComputeOnlyDiskOfferingById(Long diskOfferingId) { DiskOfferingVO off = s_diskOfferingDao.findByIdIncludingRemoved(diskOfferingId); - if (off.getType() == DiskOfferingVO.Type.Disk) { + if (off.isComputeOnly()) { return off; } return null; } + public static DiskOfferingVO findDiskOfferingById(Long diskOfferingId) { + DiskOfferingVO off = s_diskOfferingDao.findByIdIncludingRemoved(diskOfferingId); + if (!off.isComputeOnly()) { + return off; + } + return null; + } + + public static ServiceOfferingVO findServiceOfferingByComputeOnlyDiskOffering(Long diskOfferingId) { + ServiceOfferingVO off = s_serviceOfferingDao.findServiceOfferingByComputeOnlyDiskOffering(diskOfferingId); + return off; + } public static DomainVO findDomainById(Long domainId) { return s_domainDao.findByIdIncludingRemoved(domainId); } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 6f5b272dc20..57ac7d03394 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -23,6 +23,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -30,6 +31,7 @@ import java.util.stream.Stream; import javax.inject.Inject; +import com.cloud.storage.VolumeApiServiceImpl; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupResponse; @@ -124,6 +126,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -194,6 +197,7 @@ import com.cloud.network.router.VirtualNetworkApplianceManager; import com.cloud.network.security.SecurityGroupVMMapVO; import com.cloud.network.security.dao.SecurityGroupVMMapDao; import com.cloud.org.Grouping; +import com.cloud.offering.DiskOffering; import com.cloud.projects.Project; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.projects.ProjectInvitation; @@ -213,17 +217,19 @@ import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DataStoreRole; -import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.StoragePoolTagVO; +import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDetailsDao; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.template.VirtualMachineTemplate.State; @@ -366,6 +372,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private ServiceOfferingDetailsDao _srvOfferingDetailsDao; + @Inject + private DiskOfferingDao _diskOfferingDao; + @Inject private DataCenterJoinDao _dcJoinDao; @@ -445,6 +454,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private VirtualMachineManager virtualMachineManager; + @Inject + private VolumeDao volumeDao; @Inject private ResourceIconDao resourceIconDao; @@ -2077,7 +2088,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q sb.and("display", sb.entity().isDisplayVolume(), SearchCriteria.Op.EQ); sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ); - sb.and("systemUse", sb.entity().isSystemUse(), SearchCriteria.Op.NEQ); + sb.and().op("systemUse", sb.entity().isSystemUse(), SearchCriteria.Op.NEQ); + sb.or("nulltype", sb.entity().isSystemUse(), SearchCriteria.Op.NULL); + sb.cp(); + // display UserVM volumes only sb.and().op("type", sb.entity().getVmType(), SearchCriteria.Op.NIN); sb.or("nulltype", sb.entity().getVmType(), SearchCriteria.Op.NULL); @@ -2882,7 +2896,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q Filter searchFilter = new Filter(DiskOfferingJoinVO.class, "sortKey", SortKeyAscending.value(), cmd.getStartIndex(), cmd.getPageSizeVal()); searchFilter.addOrderBy(DiskOfferingJoinVO.class, "id", true); SearchCriteria sc = _diskOfferingJoinDao.createSearchCriteria(); - sc.addAnd("type", Op.EQ, DiskOfferingVO.Type.Disk); + sc.addAnd("computeOnly", Op.EQ, false); Account account = CallContext.current().getCallingAccount(); Object name = cmd.getDiskOfferingName(); @@ -2892,6 +2906,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q Boolean isRootAdmin = _accountMgr.isRootAdmin(account.getAccountId()); Boolean isRecursive = cmd.isRecursive(); Long zoneId = cmd.getZoneId(); + Long volumeId = cmd.getVolumeId(); + Long storagePoolId = cmd.getStoragePoolId(); // Keeping this logic consistent with domain specific zones // if a domainId is provided, we just return the disk offering // associated with this domain @@ -2922,6 +2938,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } + if (volumeId != null && storagePoolId != null) { + throw new InvalidParameterValueException("Both volume ID and storage pool ID are not allowed at the same time"); + } + if (keyword != null) { SearchCriteria ssc = _diskOfferingJoinDao.createSearchCriteria(); ssc.addOr("displayText", SearchCriteria.Op.LIKE, "%" + keyword + "%"); @@ -2948,6 +2968,23 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q sc.addAnd("zoneId", SearchCriteria.Op.SC, zoneSC); } + DiskOffering currentDiskOffering = null; + if (volumeId != null) { + Volume volume = volumeDao.findById(volumeId); + if (volume == null) { + throw new InvalidParameterValueException(String.format("Unable to find a volume with specified id %s", volumeId)); + } + currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId()); + if (!currentDiskOffering.isComputeOnly() && currentDiskOffering.getDiskSizeStrictness()) { + SearchCriteria ssc = _diskOfferingJoinDao.createSearchCriteria(); + ssc.addOr("diskSize", Op.EQ, volume.getSize()); + ssc.addOr("customized", SearchCriteria.Op.EQ, true); + sc.addAnd("diskSizeOrCustomized", SearchCriteria.Op.SC, ssc); + } + sc.addAnd("id", SearchCriteria.Op.NEQ, currentDiskOffering.getId()); + sc.addAnd("diskSizeStrictness", Op.EQ, currentDiskOffering.getDiskSizeStrictness()); + } + // Filter offerings that are not associated with caller's domain // Fetch the offering ids from the details table since theres no smart way to filter them in the join ... yet! Account caller = CallContext.current().getCallingAccount(); @@ -2971,6 +3008,28 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } Pair, Integer> result = _diskOfferingJoinDao.searchAndCount(sc, searchFilter); + String[] requiredTagsArray = new String[0]; + if (CollectionUtils.isNotEmpty(result.first()) && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(zoneId)) { + if (volumeId != null) { + Volume volume = volumeDao.findById(volumeId); + currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId()); + requiredTagsArray = currentDiskOffering.getTagsArray(); + } else if (storagePoolId != null) { + requiredTagsArray = _storageTagDao.getStoragePoolTags(storagePoolId).toArray(new String[0]); + } + } + if (requiredTagsArray.length != 0) { + ListIterator iteratorForTagsChecking = result.first().listIterator(); + while (iteratorForTagsChecking.hasNext()) { + DiskOfferingJoinVO offering = iteratorForTagsChecking.next(); + String offeringTags = offering.getTags(); + String[] offeringTagsArray = (offeringTags == null || offeringTags.isEmpty()) ? new String[0] : offeringTags.split(","); + if (!CollectionUtils.isSubCollection(Arrays.asList(requiredTagsArray), Arrays.asList(offeringTagsArray))) { + iteratorForTagsChecking.remove(); + } + } + } + return new Pair<>(result.first(), result.second()); } @@ -3054,6 +3113,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q sc.addAnd("id", SearchCriteria.Op.NEQ, currentVmOffering.getId()); } + if (currentVmOffering.getDiskOfferingStrictness()) { + sc.addAnd("diskOfferingId", Op.EQ, currentVmOffering.getDiskOfferingId()); + sc.addAnd("diskOfferingStrictness", Op.EQ, true); + } else { + sc.addAnd("diskOfferingStrictness", Op.EQ, false); + } + boolean isRootVolumeUsingLocalStorage = virtualMachineManager.isRootVolumeOnLocalStorage(vmId); // 1. Only return offerings with the same storage type than the storage pool where the VM's root volume is allocated @@ -3199,7 +3265,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } if (currentVmOffering != null) { - List storageTags = com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getTags()); + DiskOfferingVO diskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentVmOffering.getDiskOfferingId()); + List storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags()); if (!storageTags.isEmpty()) { SearchBuilder sb = _srvOfferingJoinDao.createSearchBuilder(); for(String tag : storageTags) { diff --git a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java index 685f30169b4..cf9395d179b 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java @@ -139,6 +139,7 @@ public class DiskOfferingJoinDaoImpl extends GenericDaoBase 0) { - if (ApiDBUtils.findServiceOfferingByUuid(volume.getDiskOfferingUuid()) != null) { - volResponse.setServiceOfferingId(volume.getDiskOfferingUuid()); - volResponse.setServiceOfferingName(volume.getDiskOfferingName()); - volResponse.setServiceOfferingDisplayText(volume.getDiskOfferingDisplayText()); + DiskOffering computeOnlyDiskOffering = ApiDBUtils.findComputeOnlyDiskOfferingById(volume.getDiskOfferingId()); + if (computeOnlyDiskOffering != null) { + ServiceOffering serviceOffering = ApiDBUtils.findServiceOfferingByComputeOnlyDiskOffering(volume.getDiskOfferingId()); + volResponse.setServiceOfferingId(String.valueOf(serviceOffering.getId())); + volResponse.setServiceOfferingName(serviceOffering.getName()); + volResponse.setServiceOfferingDisplayText(serviceOffering.getDisplayText()); } else { volResponse.setDiskOfferingId(volume.getDiskOfferingUuid()); volResponse.setDiskOfferingName(volume.getDiskOfferingName()); diff --git a/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java index 707c2a3e3b0..2013c37161d 100644 --- a/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java @@ -29,7 +29,6 @@ import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; import com.cloud.offering.DiskOffering; -import com.cloud.offering.DiskOffering.Type; import com.cloud.storage.Storage; import com.cloud.utils.db.GenericDao; @@ -62,9 +61,6 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, @Column(name = "use_local_storage") private boolean useLocalStorage; - @Column(name = "system_use") - private boolean systemUse; - @Column(name = "customized") private boolean customized; @@ -122,8 +118,8 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, @Column(name = "cache_mode") String cacheMode; - @Column(name = "type") - Type type; + @Column(name = "compute_only") + boolean computeOnly; @Column(name = GenericDao.CREATED_COLUMN) private Date created; @@ -162,6 +158,9 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, @Column(name = "vsphere_storage_policy") String vsphereStoragePolicy; + @Column(name = "disk_size_strictness") + boolean diskSizeStrictness; + public DiskOfferingJoinVO() { } @@ -199,10 +198,6 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, return useLocalStorage; } - public boolean isSystemUse() { - return systemUse; - } - public boolean isCustomized() { return customized; } @@ -247,8 +242,8 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, return sortKey; } - public Type getType() { - return type; + public boolean isComputeOnly() { + return computeOnly; } public Long getBytesReadRate() { @@ -350,4 +345,8 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, public String getVsphereStoragePolicy() { return vsphereStoragePolicy; } + + public boolean getDiskSizeStrictness() { + return diskSizeStrictness; + } } diff --git a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java index e9b2f2d79b6..825f8b5cdfb 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java @@ -196,6 +196,21 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit @Column(name = "dynamic_scaling_enabled") private boolean dynamicScalingEnabled; + @Column(name = "disk_offering_strictness") + private boolean diskOfferingStrictness; + + @Column(name = "disk_offering_id") + private long diskOfferingId; + + @Column(name = "disk_offering_uuid") + private String diskOfferingUuid; + + @Column(name = "disk_offering_name") + private String diskOfferingName; + + @Column(name = "disk_offering_display_text") + private String diskOfferingDisplayText; + public ServiceOfferingJoinVO() { } @@ -408,4 +423,24 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit public void setDynamicScalingEnabled(boolean dynamicScalingEnabled) { this.dynamicScalingEnabled = dynamicScalingEnabled; } + + public boolean getDiskOfferingStrictness() { + return diskOfferingStrictness; + } + + public long getDiskOfferingId() { + return diskOfferingId; + } + + public String getDiskOfferingUuid() { + return diskOfferingUuid; + } + + public String getDiskOfferingName() { + return diskOfferingName; + } + + public String getDiskOfferingDisplayText() { + return diskOfferingDisplayText; + } } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 56342107e6e..7d98dfb1869 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -2865,6 +2865,14 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } + final Long diskOfferingId = cmd.getDiskOfferingId(); + if (diskOfferingId != null) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); + if ((diskOffering == null) || diskOffering.isComputeOnly()) { + throw new InvalidParameterValueException("Please specify a valid disk offering."); + } + } + return createServiceOffering(userId, cmd.isSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber, memory, cpuSpeed, cmd.getDisplayText(), cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainIds(), cmd.getZoneIds(), cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner(), details, cmd.getRootDiskSize(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(), @@ -2872,7 +2880,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati cmd.getBytesWriteRate(), cmd.getBytesWriteRateMax(), cmd.getBytesWriteRateMaxLength(), cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(), cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(), cmd.getIopsWriteRateMaxLength(), - cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(), storagePolicyId, cmd.getDynamicScalingEnabled()); + cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(), storagePolicyId, cmd.getDynamicScalingEnabled(), diskOfferingId, cmd.getDiskOfferingStrictness(), cmd.isCustomized()); } protected ServiceOfferingVO createServiceOffering(final long userId, final boolean isSystem, final VirtualMachine.Type vmType, @@ -2883,7 +2891,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength, Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength, Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength, - final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID, final boolean dynamicScalingEnabled) { + final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID, final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness, final boolean isCustomized) { // Filter child domains when both parent and child domains are present List filteredDomainIds = filterChildSubDomains(domainIds); @@ -2913,55 +2921,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati tags = com.cloud.utils.StringUtils.cleanupTags(tags); - ServiceOfferingVO offering = new ServiceOfferingVO(name, cpu, ramSize, speed, networkRate, null, offerHA, - limitResourceUse, volatileVm, displayText, typedProvisioningType, localStorageRequired, false, tags, isSystem, vmType, - hostTag, deploymentPlanner, dynamicScalingEnabled); - - if (Boolean.TRUE.equals(isCustomizedIops) || isCustomizedIops == null) { - minIops = null; - maxIops = null; - } else { - if (minIops == null && maxIops == null) { - minIops = 0L; - maxIops = 0L; - } else { - if (minIops == null || minIops <= 0) { - throw new InvalidParameterValueException("The min IOPS must be greater than 0."); - } - - if (maxIops == null) { - maxIops = 0L; - } - - if (minIops > maxIops) { - throw new InvalidParameterValueException("The min IOPS must be less than or equal to the max IOPS."); - } - } - } - - if (rootDiskSizeInGiB != null && rootDiskSizeInGiB <= 0L) { - throw new InvalidParameterValueException(String.format("The Root disk size is of %s GB but it must be greater than 0.", rootDiskSizeInGiB)); - } else if (rootDiskSizeInGiB != null) { - long rootDiskSizeInBytes = rootDiskSizeInGiB * GiB_TO_BYTES; - offering.setDiskSize(rootDiskSizeInBytes); - } - - offering.setCustomizedIops(isCustomizedIops); - offering.setMinIops(minIops); - offering.setMaxIops(maxIops); - - setBytesRate(offering, bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength); - setIopsRate(offering, iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength); - - if(cacheMode != null) { - offering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase())); - } - - if (hypervisorSnapshotReserve != null && hypervisorSnapshotReserve < 0) { - throw new InvalidParameterValueException("If provided, Hypervisor Snapshot Reserve must be greater than or equal to 0."); - } - - offering.setHypervisorSnapshotReserve(hypervisorSnapshotReserve); + ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, networkRate, null, offerHA, + limitResourceUse, volatileVm, displayText, isSystem, vmType, + hostTag, deploymentPlanner, dynamicScalingEnabled, isCustomized); List detailsVO = new ArrayList(); if (details != null) { @@ -2996,51 +2958,137 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati // Add in disk offering details continue; } - detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), detailEntry.getKey(), detailEntryValue, true)); + detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), detailEntry.getKey(), detailEntryValue, true)); } } if (storagePolicyID != null) { - detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), ApiConstants.STORAGE_POLICY, String.valueOf(storagePolicyID), false)); + detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.STORAGE_POLICY, String.valueOf(storagePolicyID), false)); } - if ((offering = _serviceOfferingDao.persist(offering)) != null) { + serviceOffering.setDiskOfferingStrictness(diskOfferingStrictness); + + DiskOfferingVO diskOffering = null; + if (diskOfferingId == null) { + diskOffering = createDiskOfferingInternal(userId, isSystem, vmType, + name, cpu, ramSize, speed, displayText, typedProvisioningType, localStorageRequired, + offerHA, limitResourceUse, volatileVm, tags, domainIds, zoneIds, hostTag, + networkRate, deploymentPlanner, details, rootDiskSizeInGiB, isCustomizedIops, minIops, maxIops, + bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, + bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength, + iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, + iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength, + hypervisorSnapshotReserve, cacheMode, storagePolicyID); + } else { + diskOffering = _diskOfferingDao.findById(diskOfferingId); + } + if (diskOffering != null) { + serviceOffering.setDiskOfferingId(diskOffering.getId()); + } else { + return null; + } + + if ((serviceOffering = _serviceOfferingDao.persist(serviceOffering)) != null) { for (Long domainId : filteredDomainIds) { - detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); + detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); } if (CollectionUtils.isNotEmpty(zoneIds)) { for (Long zoneId : zoneIds) { - detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId), false)); + detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId), false)); } } - if (!detailsVO.isEmpty()) { + if (CollectionUtils.isNotEmpty(detailsVO)) { for (ServiceOfferingDetailsVO detail : detailsVO) { - detail.setResourceId(offering.getId()); + detail.setResourceId(serviceOffering.getId()); } _serviceOfferingDetailsDao.saveDetails(detailsVO); } - if (details != null && !details.isEmpty()) { - List diskDetailsVO = new ArrayList(); - // Support disk offering details for below parameters - if (details.containsKey(Volume.BANDWIDTH_LIMIT_IN_MBPS)) { - diskDetailsVO.add(new DiskOfferingDetailVO(offering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, details.get(Volume.BANDWIDTH_LIMIT_IN_MBPS), false)); - } - if (details.containsKey(Volume.IOPS_LIMIT)) { - diskDetailsVO.add(new DiskOfferingDetailVO(offering.getId(), Volume.IOPS_LIMIT, details.get(Volume.IOPS_LIMIT), false)); - } - if (!diskDetailsVO.isEmpty()) { - diskOfferingDetailsDao.saveDetails(diskDetailsVO); - } - } - - CallContext.current().setEventDetails("Service offering id=" + offering.getId()); - return offering; + CallContext.current().setEventDetails("Service offering id=" + serviceOffering.getId()); + return serviceOffering; } else { return null; } } + private DiskOfferingVO createDiskOfferingInternal(final long userId, final boolean isSystem, final VirtualMachine.Type vmType, + final String name, final Integer cpu, final Integer ramSize, final Integer speed, final String displayText, final ProvisioningType typedProvisioningType, final boolean localStorageRequired, + final boolean offerHA, final boolean limitResourceUse, final boolean volatileVm, String tags, final List domainIds, List zoneIds, final String hostTag, + final Integer networkRate, final String deploymentPlanner, final Map details, Long rootDiskSizeInGiB, final Boolean isCustomizedIops, Long minIops, Long maxIops, + Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength, + Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength, + Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength, + Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength, + final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID) { + + DiskOfferingVO diskOffering = new DiskOfferingVO(name, displayText, typedProvisioningType, false, tags, false, localStorageRequired, false); + + if (Boolean.TRUE.equals(isCustomizedIops) || isCustomizedIops == null) { + minIops = null; + maxIops = null; + } else { + if (minIops == null && maxIops == null) { + minIops = 0L; + maxIops = 0L; + } else { + if (minIops == null || minIops <= 0) { + throw new InvalidParameterValueException("The min IOPS must be greater than 0."); + } + + if (maxIops == null) { + maxIops = 0L; + } + + if (minIops > maxIops) { + throw new InvalidParameterValueException("The min IOPS must be less than or equal to the max IOPS."); + } + } + } + + if (rootDiskSizeInGiB != null && rootDiskSizeInGiB <= 0L) { + throw new InvalidParameterValueException(String.format("The Root disk size is of %s GB but it must be greater than 0.", rootDiskSizeInGiB)); + } else if (rootDiskSizeInGiB != null) { + long rootDiskSizeInBytes = rootDiskSizeInGiB * GiB_TO_BYTES; + diskOffering.setDiskSize(rootDiskSizeInBytes); + } + + diskOffering.setCustomizedIops(isCustomizedIops); + diskOffering.setMinIops(minIops); + diskOffering.setMaxIops(maxIops); + + setBytesRate(diskOffering, bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength); + setIopsRate(diskOffering, iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength); + + if(cacheMode != null) { + diskOffering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase())); + } + + if (hypervisorSnapshotReserve != null && hypervisorSnapshotReserve < 0) { + throw new InvalidParameterValueException("If provided, Hypervisor Snapshot Reserve must be greater than or equal to 0."); + } + + diskOffering.setHypervisorSnapshotReserve(hypervisorSnapshotReserve); + + if ((diskOffering = _diskOfferingDao.persist(diskOffering)) != null) { + if (details != null && !details.isEmpty()) { + List diskDetailsVO = new ArrayList(); + // Support disk offering details for below parameters + if (details.containsKey(Volume.BANDWIDTH_LIMIT_IN_MBPS)) { + diskDetailsVO.add(new DiskOfferingDetailVO(diskOffering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, details.get(Volume.BANDWIDTH_LIMIT_IN_MBPS), false)); + } + if (details.containsKey(Volume.IOPS_LIMIT)) { + diskDetailsVO.add(new DiskOfferingDetailVO(diskOffering.getId(), Volume.IOPS_LIMIT, details.get(Volume.IOPS_LIMIT), false)); + } + if (!diskDetailsVO.isEmpty()) { + diskOfferingDetailsDao.saveDetails(diskDetailsVO); + } + } + } else { + return null; + } + + return diskOffering; + } private void setIopsRate(DiskOffering offering, Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength, Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength) { if (iopsReadRate != null && iopsReadRate > 0) { offering.setIopsReadRate(iopsReadRate); @@ -3196,7 +3244,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati offering.setSortKey(sortKey); } - updateOfferingTagsIfIsNotNull(storageTags, offering); + DiskOfferingVO diskOffering = _diskOfferingDao.findById(offeringHandle.getDiskOfferingId()); + updateOfferingTagsIfIsNotNull(storageTags, diskOffering); + _diskOfferingDao.update(diskOffering.getId(), diskOffering); updateServiceOfferingHostTagsIfNotNull(hostTags, offering); @@ -3261,7 +3311,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength, Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength, Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength, - final Integer hypervisorSnapshotReserve, String cacheMode, final Map details, final Long storagePolicyID) { + final Integer hypervisorSnapshotReserve, String cacheMode, final Map details, final Long storagePolicyID, final boolean diskSizeStrictness) { long diskSize = 0;// special case for custom disk offerings if (numGibibytes != null && numGibibytes <= 0) { throw new InvalidParameterValueException("Please specify a disk size of at least 1 Gb."); @@ -3343,6 +3393,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } newDiskOffering.setHypervisorSnapshotReserve(hypervisorSnapshotReserve); + newDiskOffering.setDiskSizeStrictness(diskSizeStrictness); CallContext.current().setEventDetails("Disk offering id=" + newDiskOffering.getId()); final DiskOfferingVO offering = _diskOfferingDao.persist(newDiskOffering); @@ -3391,6 +3442,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final List zoneIds = cmd.getZoneIds(); final Map details = cmd.getDetails(); final Long storagePolicyId = cmd.getStoragePolicy(); + final boolean diskSizeStrictness = cmd.getDiskSizeStrictness(); // check if valid domain if (CollectionUtils.isNotEmpty(domainIds)) { @@ -3466,7 +3518,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati localStorageRequired, isDisplayOfferingEnabled, isCustomizedIops, minIops, maxIops, bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength, iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength, - hypervisorSnapshotReserve, cacheMode, details, storagePolicyId); + hypervisorSnapshotReserve, cacheMode, details, storagePolicyId, diskSizeStrictness); } /** @@ -3858,6 +3910,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("unable to find service offering " + offeringId); } + // Verify disk offering id mapped to the service offering + final DiskOfferingVO diskOffering = _diskOfferingDao.findById(offering.getDiskOfferingId()); + if (diskOffering == null) { + throw new InvalidParameterValueException("unable to find disk offering " + offering.getDiskOfferingId() + " mapped to the service offering " + offeringId); + } + if (offering.getDefaultUse()) { throw new InvalidParameterValueException("Default service offerings cannot be deleted"); } @@ -3882,7 +3940,13 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } annotationDao.removeByEntityType(AnnotationService.EntityType.SERVICE_OFFERING.name(), offering.getUuid()); - offering.setState(DiskOffering.State.Inactive); + if (diskOffering.isComputeOnly()) { + diskOffering.setState(DiskOffering.State.Inactive); + if (!_diskOfferingDao.update(diskOffering.getId(), diskOffering)) { + throw new CloudRuntimeException(String.format("Unable to delete disk offering %s mapped to the service offering %s", diskOffering.getUuid(), offering.getUuid())); + } + } + offering.setState(ServiceOffering.State.Inactive); if (_serviceOfferingDao.update(offeringId, offering)) { CallContext.current().setEventDetails("Service offering id=" + offeringId); return true; diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 436828f6293..49937a9d192 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -17,7 +17,6 @@ package com.cloud.deploy; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -113,7 +112,6 @@ import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.ScopeType; -import com.cloud.storage.Storage; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolHostVO; @@ -1404,6 +1402,7 @@ StateListener, Configurable { List allVolumes = new ArrayList<>(); allVolumes.addAll(volumesOrderBySizeDesc); + List> volumeDiskProfilePair = getVolumeDiskProfilePairs(allVolumes); for (StoragePool storagePool : suitablePools) { haveEnoughSpace = false; hostCanAccessPool = false; @@ -1412,7 +1411,7 @@ StateListener, Configurable { hostCanAccessPool = true; if (potentialHost.getHypervisorType() == HypervisorType.VMware) { try { - boolean isStoragePoolStoragepolicyComplaince = _storageMgr.isStoragePoolCompliantWithStoragePolicy(allVolumes, storagePool); + boolean isStoragePoolStoragepolicyComplaince = _storageMgr.isStoragePoolCompliantWithStoragePolicy(volumeDiskProfilePair, storagePool); if (!isStoragePoolStoragepolicyComplaince) { continue; } @@ -1448,10 +1447,10 @@ StateListener, Configurable { else requestVolumes = new ArrayList(); requestVolumes.add(vol); - + List> volumeDiskProfilePair = getVolumeDiskProfilePairs(requestVolumes); if (potentialHost.getHypervisorType() == HypervisorType.VMware) { try { - boolean isStoragePoolStoragepolicyComplaince = _storageMgr.isStoragePoolCompliantWithStoragePolicy(requestVolumes, potentialSPool); + boolean isStoragePoolStoragepolicyComplaince = _storageMgr.isStoragePoolCompliantWithStoragePolicy(volumeDiskProfilePair, potentialSPool); if (!isStoragePoolStoragepolicyComplaince) { continue; } @@ -1461,8 +1460,8 @@ StateListener, Configurable { } } - if (!_storageMgr.storagePoolHasEnoughIops(requestVolumes, potentialSPool) || - !_storageMgr.storagePoolHasEnoughSpace(requestVolumes, potentialSPool, potentialHost.getClusterId())) + if (!_storageMgr.storagePoolHasEnoughIops(volumeDiskProfilePair, potentialSPool) || + !_storageMgr.storagePoolHasEnoughSpace(volumeDiskProfilePair, potentialSPool, potentialHost.getClusterId())) continue; volumeAllocationMap.put(potentialSPool, requestVolumes); } @@ -1498,6 +1497,16 @@ StateListener, Configurable { return null; } + private List> getVolumeDiskProfilePairs(List volumes) { + List> volumeDiskProfilePairs = new ArrayList<>(); + for (Volume volume: volumes) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + DiskProfile diskProfile = new DiskProfile(volume, diskOffering, _volsDao.getHypervisorType(volume.getId())); + volumeDiskProfilePairs.add(new Pair<>(volume, diskProfile)); + } + return volumeDiskProfilePairs; + } + /** * True if: * - Affinity is not enabled (preferred host is empty) @@ -1639,10 +1648,11 @@ StateListener, Configurable { DiskOfferingVO diskOffering = _diskOfferingDao.findById(toBeCreated.getDiskOfferingId()); - if ((vmProfile.getTemplate().getFormat() == Storage.ImageFormat.ISO || toBeCreated.getVolumeType() == Volume.Type.ROOT) - && vmProfile.getServiceOffering().getTagsArray().length != 0) { - diskOffering.setTagsArray(Arrays.asList(vmProfile.getServiceOffering().getTagsArray())); - } + //FR123 check how is it different for service offering getTagsArray and disk offering's + //if ((vmProfile.getTemplate().getFormat() == Storage.ImageFormat.ISO || toBeCreated.getVolumeType() == Volume.Type.ROOT) + // && vmProfile.getServiceOffering().getTagsArray().length != 0) { + // diskOffering.setTagsArray(Arrays.asList(vmProfile.getServiceOffering().getTagsArray())); + //} DiskProfile diskProfile = new DiskProfile(toBeCreated, diskOffering, vmProfile.getHypervisorType()); boolean useLocalStorage = false; @@ -1656,19 +1666,6 @@ StateListener, Configurable { } } else { useLocalStorage = diskOffering.isUseLocalStorage(); - - // TODO: this is a hacking fix for the problem of deploy - // ISO-based VM on local storage - // when deploying VM based on ISO, we have a service offering - // and an additional disk offering, use-local storage flag is - // actually - // saved in service offering, override the flag from service - // offering when it is a ROOT disk - if (!useLocalStorage && vmProfile.getServiceOffering().isUseLocalStorage()) { - if (toBeCreated.getVolumeType() == Volume.Type.ROOT) { - useLocalStorage = true; - } - } } diskProfile.setUseLocalStorage(useLocalStorage); diff --git a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java index e05bc5cb5bc..82b378a26ea 100644 --- a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java +++ b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java @@ -276,7 +276,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur for (VMInstanceVO vm : reorderedVMList) { ServiceOfferingVO vmOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); - if (vmOffering.isUseLocalStorage()) { + if (_itMgr.isRootVolumeOnLocalStorage(vm.getId())) { if (s_logger.isDebugEnabled()){ s_logger.debug("Skipping HA on vm " + vm + ", because it uses local storage. Its fate is tied to the host."); } diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java index 11ecfb503a2..36b419beda1 100644 --- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; +import com.cloud.offering.DiskOffering; import org.apache.log4j.Logger; import com.google.gson.Gson; @@ -1307,6 +1308,11 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScale throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId); } + DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, serviceOffering.getDiskOfferingId()); + if (diskOffering == null) { + throw new InvalidParameterValueException("Unable to find disk offering: " + serviceOffering.getDiskOfferingId()); + } + VirtualMachineTemplate template = _entityMgr.findById(VirtualMachineTemplate.class, templateId); // Make sure a valid template ID was specified if (template == null) { @@ -1314,8 +1320,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScale } if (!zone.isLocalStorageEnabled()) { - if (serviceOffering.isUseLocalStorage()) { - throw new InvalidParameterValueException("Zone is not configured to use local storage but service offering " + serviceOffering.getName() + " uses it"); + if (diskOffering.isUseLocalStorage()) { + throw new InvalidParameterValueException("Zone is not configured to use local storage but disk offering " + diskOffering.getName() + " associated to the service offering " + serviceOffering.getName() + " uses it"); } } @@ -1325,18 +1331,18 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScale vm = _userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, - null, true, null, null, null, null, null, null, null, true); + null, true, null, null, null, null, null, null, null, true, null); } else { if (zone.isSecurityGroupEnabled()) { vm = _userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, null, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, - null, null, true, null, null, null, null, null, null, null, true); + null, null, true, null, null, null, null, null, null, null, true, null); } else { vm = _userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), - null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null, null, null, null, true, null); + null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null, null, null, null, true, null, null); } } diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 39d4c5f55df..40d5e677e50 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -45,6 +45,7 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.offering.DiskOffering; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.alert.AlertService.AlertType; import org.apache.cloudstack.api.command.admin.router.RebootRouterCmd; @@ -417,6 +418,10 @@ Configurable, StateListener, List> listStoragePoolsForMigrationOfVolume(final Long volumeId) { + + Pair, List> allPoolsAndSuitablePoolsPair = listStoragePoolsForMigrationOfVolumeInternal(volumeId, null, null, null, null, false); + List allPools = allPoolsAndSuitablePoolsPair.first(); + List suitablePools = allPoolsAndSuitablePoolsPair.second(); + List avoidPools = new ArrayList<>(); + + final VolumeVO volume = _volumeDao.findById(volumeId); + StoragePool srcVolumePool = _poolDao.findById(volume.getPoolId()); + if (srcVolumePool.getParent() != 0L) { + StoragePool datastoreCluster = _poolDao.findById(srcVolumePool.getParent()); + avoidPools.add(datastoreCluster); + } + abstractDataStoreClustersList((List) allPools, new ArrayList()); + abstractDataStoreClustersList((List) suitablePools, avoidPools); + return new Pair, List>(allPools, suitablePools); + } + + public Pair, List> listStoragePoolsForMigrationOfVolumeInternal(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool) { final Account caller = getCaller(); if (!_accountMgr.isRootAdmin(caller.getId())) { if (s_logger.isDebugEnabled()) { @@ -1497,6 +1516,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe throw ex; } + Long diskOfferingId = volume.getDiskOfferingId(); + if (newDiskOfferingId != null) { + diskOfferingId = newDiskOfferingId; + } + // Volume must be attached to an instance for live migration. List allPools = new ArrayList(); List suitablePools = new ArrayList(); @@ -1550,24 +1574,35 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe Host vmHost = hostClusterPair.first(); List clusters = hostClusterPair.second(); allPools = getAllStoragePoolCompatibleWithVolumeSourceStoragePool(srcVolumePool, hypervisorType, clusters); - allPools.remove(srcVolumePool); + ExcludeList avoid = new ExcludeList(); + if (!keepSourceStoragePool) { + allPools.remove(srcVolumePool); + avoid.addPool(srcVolumePool.getId()); + } if (vm != null) { - suitablePools = findAllSuitableStoragePoolsForVm(volume, vm, vmHost, srcVolumePool, + suitablePools = findAllSuitableStoragePoolsForVm(volume, diskOfferingId, newSize, newMinIops, newMaxIops, vm, vmHost, avoid, CollectionUtils.isNotEmpty(clusters) ? clusters.get(0) : null, hypervisorType); } else { - suitablePools = findAllSuitableStoragePoolsForDetachedVolume(volume, allPools); + suitablePools = findAllSuitableStoragePoolsForDetachedVolume(volume, diskOfferingId, allPools); } - List avoidPools = new ArrayList<>(); - if (srcVolumePool.getParent() != 0L) { - StoragePool datastoreCluster = _poolDao.findById(srcVolumePool.getParent()); - avoidPools.add(datastoreCluster); - } - abstractDataStoreClustersList((List) allPools, new ArrayList()); - abstractDataStoreClustersList((List) suitablePools, avoidPools); + removeDataStoreClusterParents((List) allPools); + removeDataStoreClusterParents((List) suitablePools); return new Pair, List>(allPools, suitablePools); } - private void abstractDataStoreClustersList(List storagePools, List avoidPools) { + private void removeDataStoreClusterParents(List storagePools) { + Predicate childDatastorePredicate = pool -> (pool.getParent() != 0); + List childDatastores = storagePools.stream().filter(childDatastorePredicate).collect(Collectors.toList()); + if (!childDatastores.isEmpty()) { + Set parentStoragePoolIds = childDatastores.stream().map(mo -> mo.getParent()).collect(Collectors.toSet()); + for (Long parentStoragePoolId : parentStoragePoolIds) { + StoragePool parentPool = _poolDao.findById(parentStoragePoolId); + storagePools.remove(parentPool); + } + } + } + + private void abstractDataStoreClustersList(List storagePools, List avoidPools) { Predicate childDatastorePredicate = pool -> (pool.getParent() != 0); List childDatastores = storagePools.stream().filter(childDatastorePredicate).collect(Collectors.toList()); storagePools.removeAll(avoidPools); @@ -1637,10 +1672,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe * * Side note: the idea behind this method is to provide power for administrators of manually overriding deployments defined by CloudStack. */ - private List findAllSuitableStoragePoolsForVm(final VolumeVO volume, VMInstanceVO vm, Host vmHost, StoragePool srcVolumePool, Cluster srcCluster, HypervisorType hypervisorType) { + private List findAllSuitableStoragePoolsForVm(final VolumeVO volume, Long diskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, VMInstanceVO vm, Host vmHost, ExcludeList avoid, Cluster srcCluster, HypervisorType hypervisorType) { List suitablePools = new ArrayList<>(); - ExcludeList avoid = new ExcludeList(); - avoid.addPool(srcVolumePool.getId()); Long clusterId = null; Long podId = null; if (srcCluster != null) { @@ -1652,8 +1685,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); // OfflineVmwareMigration: vm might be null here; deal! - DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); DiskProfile diskProfile = new DiskProfile(volume, diskOffering, hypervisorType); + if (volume.getDiskOfferingId() != diskOfferingId) { + diskProfile.setSize(newSize); + diskProfile.setMinIops(newMinIops); + diskProfile.setMaxIops(newMaxIops); + } for (StoragePoolAllocator allocator : _storagePoolAllocators) { List pools = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL, true); @@ -1671,12 +1709,12 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe return suitablePools; } - private List findAllSuitableStoragePoolsForDetachedVolume(Volume volume, List allPools) { + private List findAllSuitableStoragePoolsForDetachedVolume(Volume volume, Long diskOfferingId, List allPools) { List suitablePools = new ArrayList<>(); if (CollectionUtils.isEmpty(allPools)) { return suitablePools; } - DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); List tags = new ArrayList<>(); String[] tagsArray = diskOffering.getTagsArray(); if (tagsArray != null && tagsArray.length > 0) { @@ -3313,6 +3351,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(UploadVolumeCmd.class); cmdList.add(DestroyVolumeCmd.class); cmdList.add(RecoverVolumeCmd.class); + cmdList.add(ChangeOfferingForVolumeCmd.class); cmdList.add(CreateStaticRouteCmd.class); cmdList.add(CreateVPCCmd.class); cmdList.add(DeleteStaticRouteCmd.class); diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 84d058dbc2b..502cc2cc544 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -2312,7 +2312,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C } @Override - public boolean storagePoolHasEnoughIops(List requestedVolumes, StoragePool pool) { + public boolean storagePoolHasEnoughIops(List> requestedVolumes, StoragePool pool) { if (requestedVolumes == null || requestedVolumes.isEmpty() || pool == null) { return false; } @@ -2330,8 +2330,13 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C long requestedIops = 0; - for (Volume requestedVolume : requestedVolumes) { + for (Pair volumeDiskProfilePair : requestedVolumes) { + Volume requestedVolume = volumeDiskProfilePair.first(); + DiskProfile diskProfile = volumeDiskProfilePair.second(); Long minIops = requestedVolume.getMinIops(); + if (requestedVolume.getDiskOfferingId() != diskProfile.getDiskOfferingId()) { + minIops = diskProfile.getMinIops(); + } if (minIops != null && minIops > 0) { requestedIops += minIops; @@ -2344,13 +2349,13 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C } @Override - public boolean storagePoolHasEnoughSpace(List volumes, StoragePool pool) { - return storagePoolHasEnoughSpace(volumes, pool, null); + public boolean storagePoolHasEnoughSpace(List> volumeDiskProfilePairs, StoragePool pool) { + return storagePoolHasEnoughSpace(volumeDiskProfilePairs, pool, null); } @Override - public boolean storagePoolHasEnoughSpace(List volumes, StoragePool pool, Long clusterId) { - if (volumes == null || volumes.isEmpty()) { + public boolean storagePoolHasEnoughSpace(List> volumeDiskProfilesList, StoragePool pool, Long clusterId) { + if (CollectionUtils.isEmpty(volumeDiskProfilesList)) { return false; } @@ -2367,10 +2372,12 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C long allocatedSizeWithTemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null); long totalAskingSize = 0; - for (Volume volume : volumes) { + for (Pair volumeDiskProfilePair : volumeDiskProfilesList) { // refreshing the volume from the DB to get latest hv_ss_reserve (hypervisor snapshot reserve) field // I could have just assigned this to "volume", but decided to make a new variable for it so that it - // might be clearer that this "volume" in "volumes" still might have an old value for hv_ss_reverse. + // might be clearer that this "volume" in "volumeDiskProfilesList" still might have an old value for hv_ss_reverse. + Volume volume = volumeDiskProfilePair.first(); + DiskProfile diskProfile = volumeDiskProfilePair.second(); VolumeVO volumeVO = _volumeDao.findById(volume.getId()); if (volumeVO.getHypervisorSnapshotReserve() == null) { @@ -2398,7 +2405,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C // A ready-state volume is already allocated in a pool, so the asking size is zero for it. // In case the volume is moving across pools or is not ready yet, the asking size has to be computed. if ((volumeVO.getState() != Volume.State.Ready) || (volumeVO.getPoolId() != pool.getId())) { - totalAskingSize += getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeVO, poolVO); + totalAskingSize += getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeVO, diskProfile, poolVO); totalAskingSize += getAskingSizeForTemplateBasedOnClusterAndStoragePool(volumeVO.getTemplateId(), clusterId, poolVO); } @@ -2427,14 +2434,16 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C } @Override - public boolean isStoragePoolCompliantWithStoragePolicy(List volumes, StoragePool pool) throws StorageUnavailableException { + public boolean isStoragePoolCompliantWithStoragePolicy(List> volumes, StoragePool pool) throws StorageUnavailableException { if (CollectionUtils.isEmpty(volumes)) { return false; } List> answers = new ArrayList>(); - for (Volume volume : volumes) { + for (Pair volumeDiskProfilePair : volumes) { String storagePolicyId = null; + Volume volume = volumeDiskProfilePair.first(); + DiskProfile diskProfile = volumeDiskProfilePair.second(); if (volume.getVolumeType() == Type.ROOT) { Long vmId = volume.getInstanceId(); if (vmId != null) { @@ -2442,7 +2451,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C storagePolicyId = _serviceOfferingDetailsDao.getDetail(vm.getServiceOfferingId(), ApiConstants.STORAGE_POLICY); } } else { - storagePolicyId = _diskOfferingDetailsDao.getDetail(volume.getDiskOfferingId(), ApiConstants.STORAGE_POLICY); + storagePolicyId = _diskOfferingDetailsDao.getDetail(diskProfile.getDiskOfferingId(), ApiConstants.STORAGE_POLICY); } if (StringUtils.isNotEmpty(storagePolicyId)) { VsphereStoragePolicyVO storagePolicyVO = _vsphereStoragePolicyDao.findById(Long.parseLong(storagePolicyId)); @@ -2556,7 +2565,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C return 0; } - private long getDataObjectSizeIncludingHypervisorSnapshotReserve(Volume volume, StoragePool pool) { + private long getDataObjectSizeIncludingHypervisorSnapshotReserve(Volume volume, DiskProfile diskProfile, StoragePool pool) { DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); @@ -2564,7 +2573,9 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C PrimaryDataStoreDriver primaryStoreDriver = (PrimaryDataStoreDriver)storeDriver; VolumeInfo volumeInfo = volFactory.getVolume(volume.getId()); - + if (volume.getDiskOfferingId() != diskProfile.getDiskOfferingId()) { + return diskProfile.getSize(); + } return primaryStoreDriver.getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo, pool); } @@ -3244,8 +3255,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C // get bytesReadRate from service_offering, disk_offering and vm.disk.throttling.bytes_read_rate @Override public Long getDiskBytesReadRate(final ServiceOffering offering, final DiskOffering diskOffering) { - if ((offering != null) && (offering.getBytesReadRate() != null) && (offering.getBytesReadRate() > 0)) { - return offering.getBytesReadRate(); + if ((diskOffering != null) && (diskOffering.getBytesReadRate() != null) && (diskOffering.getBytesReadRate() > 0)) { + return diskOffering.getBytesReadRate(); } else if ((diskOffering != null) && (diskOffering.getBytesReadRate() != null) && (diskOffering.getBytesReadRate() > 0)) { return diskOffering.getBytesReadRate(); } else { @@ -3260,9 +3271,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C // get bytesWriteRate from service_offering, disk_offering and vm.disk.throttling.bytes_write_rate @Override public Long getDiskBytesWriteRate(final ServiceOffering offering, final DiskOffering diskOffering) { - if ((offering != null) && (offering.getBytesWriteRate() != null) && (offering.getBytesWriteRate() > 0)) { - return offering.getBytesWriteRate(); - } else if ((diskOffering != null) && (diskOffering.getBytesWriteRate() != null) && (diskOffering.getBytesWriteRate() > 0)) { + if ((diskOffering != null) && (diskOffering.getBytesWriteRate() != null) && (diskOffering.getBytesWriteRate() > 0)) { return diskOffering.getBytesWriteRate(); } else { Long bytesWriteRate = Long.parseLong(_configDao.getValue(Config.VmDiskThrottlingBytesWriteRate.key())); @@ -3276,9 +3285,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C // get iopsReadRate from service_offering, disk_offering and vm.disk.throttling.iops_read_rate @Override public Long getDiskIopsReadRate(final ServiceOffering offering, final DiskOffering diskOffering) { - if ((offering != null) && (offering.getIopsReadRate() != null) && (offering.getIopsReadRate() > 0)) { - return offering.getIopsReadRate(); - } else if ((diskOffering != null) && (diskOffering.getIopsReadRate() != null) && (diskOffering.getIopsReadRate() > 0)) { + if ((diskOffering != null) && (diskOffering.getIopsReadRate() != null) && (diskOffering.getIopsReadRate() > 0)) { return diskOffering.getIopsReadRate(); } else { Long iopsReadRate = Long.parseLong(_configDao.getValue(Config.VmDiskThrottlingIopsReadRate.key())); @@ -3292,9 +3299,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C // get iopsWriteRate from service_offering, disk_offering and vm.disk.throttling.iops_write_rate @Override public Long getDiskIopsWriteRate(final ServiceOffering offering, final DiskOffering diskOffering) { - if ((offering != null) && (offering.getIopsWriteRate() != null) && (offering.getIopsWriteRate() > 0)) { - return offering.getIopsWriteRate(); - } else if ((diskOffering != null) && (diskOffering.getIopsWriteRate() != null) && (diskOffering.getIopsWriteRate() > 0)) { + if ((diskOffering != null) && (diskOffering.getIopsWriteRate() != null) && (diskOffering.getIopsWriteRate() > 0)) { return diskOffering.getIopsWriteRate(); } else { Long iopsWriteRate = Long.parseLong(_configDao.getValue(Config.VmDiskThrottlingIopsWriteRate.key())); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 345e83f5c10..2f10c78627b 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -33,7 +33,17 @@ import java.util.concurrent.ExecutionException; import javax.inject.Inject; +import com.cloud.api.query.dao.ServiceOfferingJoinDao; +import com.cloud.api.query.vo.ServiceOfferingJoinVO; +import com.cloud.server.ManagementService; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.vm.DiskProfile; +import com.cloud.vm.UserVmDetailVO; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd; import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd; @@ -99,8 +109,6 @@ import com.cloud.agent.api.ModifyTargetsCommand; import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.DiskTO; import com.cloud.api.ApiDBUtils; -import com.cloud.api.query.dao.ServiceOfferingJoinDao; -import com.cloud.api.query.vo.ServiceOfferingJoinVO; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.Resource.ResourceType; @@ -247,6 +255,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic @Inject private DiskOfferingDao _diskOfferingDao; @Inject + private ServiceOfferingDao _serviceOfferingDao; + @Inject private DiskOfferingDetailsDao _diskOfferingDetailsDao; @Inject private AccountDao _accountDao; @@ -290,6 +300,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic public TaggedResourceService taggedResourceService; @Inject VirtualMachineManager virtualMachineManager; + @Inject + private ManagementService managementService; protected Gson _gson; @@ -309,10 +321,14 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic public static final ConfigKey AllowUserExpungeRecoverVolume = new ConfigKey("Advanced", Boolean.class, "allow.user.expunge.recover.volume", "true", "Determines whether users can expunge or recover their volume", true, ConfigKey.Scope.Account); + public static final ConfigKey MatchStoragePoolTagsWithDiskOffering = new ConfigKey("Advanced", Boolean.class, "match.storage.pool.tags.with.disk.offering", "true", + "If true, volume's disk offering can be changed only with the matched storage tags", true, ConfigKey.Scope.Zone); + private long _maxVolumeSizeInGb; private final StateMachine2 _volStateMachine; private static final Set STATES_VOLUME_CANNOT_BE_DESTROYED = new HashSet<>(Arrays.asList(Volume.State.Destroy, Volume.State.Expunging, Volume.State.Expunged, Volume.State.Allocated)); + private static final long GiB_TO_BYTES = 1024 * 1024 * 1024; protected VolumeApiServiceImpl() { _volStateMachine = Volume.State.getStateMachine(); @@ -471,7 +487,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // Check that the the disk offering specified is valid if (diskOfferingId != null) { DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); - if ((diskOffering == null) || diskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(diskOffering.getType())) { + if ((diskOffering == null) || diskOffering.getRemoved() != null || diskOffering.isComputeOnly()) { throw new InvalidParameterValueException("Please specify a valid disk offering."); } if (!diskOffering.isCustomized()) { @@ -632,7 +648,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // Check that the the disk offering is specified diskOffering = _diskOfferingDao.findById(diskOfferingId); - if ((diskOffering == null) || diskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(diskOffering.getType())) { + if ((diskOffering == null) || diskOffering.getRemoved() != null || diskOffering.isComputeOnly()) { throw new InvalidParameterValueException("Please specify a valid disk offering."); } @@ -799,7 +815,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } private VolumeVO commitVolume(final CreateVolumeCmd cmd, final Account caller, final Account owner, final Boolean displayVolume, final Long zoneId, final Long diskOfferingId, - final Storage.ProvisioningType provisioningType, final Long size, final Long minIops, final Long maxIops, final VolumeVO parentVolume, final String userSpecifiedName, final String uuid, final Map details) { + final Storage.ProvisioningType provisioningType, final Long size, final Long minIops, final Long maxIops, final VolumeVO parentVolume, final String userSpecifiedName, final String uuid, final Map details) { return Transaction.execute(new TransactionCallback() { @Override public VolumeVO doInTransaction(TransactionStatus status) { @@ -936,10 +952,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic @DB @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true) public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationException { - Long newSize; - Long newMinIops; - Long newMaxIops; - Integer newHypervisorSnapshotReserve; + Long newSize = cmd.getSize(); + Long newMinIops = cmd.getMinIops(); + Long newMaxIops = cmd.getMaxIops(); + Integer newHypervisorSnapshotReserve = null; boolean shrinkOk = cmd.isShrinkOk(); VolumeVO volume = _volsDao.findById(cmd.getEntityId()); @@ -976,14 +992,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // if we are to use the existing disk offering if (newDiskOffering == null) { - newSize = cmd.getSize(); newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve(); // if the caller is looking to change the size of the volume if (newSize != null) { - if (!diskOffering.isCustomized() && !volume.getVolumeType().equals(Volume.Type.ROOT)) { - throw new InvalidParameterValueException("To change a volume's size without providing a new disk offering, its current disk offering must be " - + "customizable or it must be a root volume (if providing a disk offering, make sure it is different from the current disk offering)."); + if (diskOffering.getDiskSizeStrictness()) { + throw new InvalidParameterValueException(String.format("Resize of volume %s is not allowed, since disk size is strictly fixed as per the disk offering", volume.getUuid())); } if (isNotPossibleToResize(volume, diskOffering)) { @@ -1028,6 +1042,23 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Requested disk offering has been removed."); } + if (diskOffering.getDiskSizeStrictness() != newDiskOffering.getDiskSizeStrictness()) { + throw new InvalidParameterValueException("Disk offering size strictness does not match with new disk offering"); + } + + if (diskOffering.getDiskSizeStrictness() && (diskOffering.getDiskSize() != newDiskOffering.getDiskSize())) { + throw new InvalidParameterValueException(String.format("Resize volume for %s is not allowed since disk offering's size is fixed", volume.getName())); + } + + Long instanceId = volume.getInstanceId(); + VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId); + if (volume.getVolumeType().equals(Volume.Type.ROOT)) { + ServiceOfferingVO serviceOffering = _serviceOfferingDao.findById(vmInstanceVO.getServiceOfferingId()); + if (serviceOffering != null && serviceOffering.getDiskOfferingStrictness()) { + throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] with new disk offering since existing disk offering is strictly assigned to the ROOT volume.", volume.getName())); + } + } + if (diskOffering.getTags() != null) { if (!com.cloud.utils.StringUtils.areTagsEqual(diskOffering.getTags(), newDiskOffering.getTags())) { throw new InvalidParameterValueException("The tags on the new and old disk offerings must match."); @@ -1038,7 +1069,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic _configMgr.checkDiskOfferingAccess(_accountMgr.getActiveAccountById(volume.getAccountId()), newDiskOffering, _dcDao.findById(volume.getDataCenterId())); - if (newDiskOffering.getDiskSize() > 0 && DiskOfferingVO.Type.Service.equals(newDiskOffering.getType())) { + if (newDiskOffering.getDiskSize() > 0 && !newDiskOffering.isComputeOnly()) { newSize = newDiskOffering.getDiskSize(); } else if (newDiskOffering.isCustomized()) { newSize = cmd.getSize(); @@ -1056,9 +1087,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic newSize = newDiskOffering.getDiskSize(); } - - Long instanceId = volume.getInstanceId(); - VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId); checkIfVolumeIsRootAndVmIsRunning(newSize, volume, vmInstanceVO); if (newDiskOffering.isCustomizedIops() != null && newDiskOffering.isCustomizedIops()) { @@ -1250,7 +1278,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } private VolumeVO orchestrateResizeVolume(long volumeId, long currentSize, long newSize, Long newMinIops, Long newMaxIops, Integer newHypervisorSnapshotReserve, Long newDiskOfferingId, - boolean shrinkOk) { + boolean shrinkOk) { final VolumeVO volume = _volsDao.findById(volumeId); UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); @@ -1355,6 +1383,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } _volsDao.update(volume.getId(), volume); + if (userVm != null) { + UserVmDetailVO userVmDetailVO = userVmDetailsDao.findDetail(userVm.getId(), VmDetailConstants.ROOT_DISK_SIZE); + if (userVmDetailVO != null) { + userVmDetailVO.setValue(String.valueOf(newSize/ GiB_TO_BYTES)); + userVmDetailsDao.update(userVmDetailVO.getId(), userVmDetailVO); + } + } /* Update resource count for the account on primary storage resource */ if (!shrinkOk) { @@ -1602,6 +1637,349 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return volume; } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CHANGE_DISK_OFFERING, eventDescription = "Changing disk offering of a volume") + public Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException { + Long newSize = cmd.getSize(); + Long newMinIops = cmd.getMinIops(); + Long newMaxIops = cmd.getMaxIops(); + Long newDiskOfferingId = cmd.getNewDiskOfferingId(); + boolean shrinkOk = cmd.isShrinkOk(); + boolean autoMigrateVolume = cmd.getAutoMigrate(); + + VolumeVO volume = _volsDao.findById(cmd.getId()); + if (volume == null) { + throw new InvalidParameterValueException("No such volume"); + } + + /* Does the caller have authority to act on this volume? */ + _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume); + + return changeDiskOfferingForVolumeInternal(volume, newDiskOfferingId, newSize, newMinIops, newMaxIops, autoMigrateVolume, shrinkOk); + } + + private Volume changeDiskOfferingForVolumeInternal(VolumeVO volume, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean autoMigrateVolume, boolean shrinkOk) throws ResourceAllocationException { + DiskOfferingVO existingDiskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newDiskOfferingId); + Integer newHypervisorSnapshotReserve = null; + + boolean volumeMigrateRequired = false; + boolean volumeResizeRequired = false; + + // VALIDATIONS + Long updateNewSize[] = {newSize}; + Long updateNewMinIops[] = {newMinIops}; + Long updateNewMaxIops[] = {newMaxIops}; + Integer updateNewHypervisorSnapshotReserve[] = {newHypervisorSnapshotReserve}; + validateVolumeResizeWithNewDiskOfferingAndLoad(volume, existingDiskOffering, newDiskOffering, updateNewSize, updateNewMinIops, updateNewMaxIops, updateNewHypervisorSnapshotReserve); + newSize = updateNewSize[0]; + newMinIops = updateNewMinIops[0]; + newMaxIops = updateNewMaxIops[0]; + newHypervisorSnapshotReserve = updateNewHypervisorSnapshotReserve[0]; + long currentSize = volume.getSize(); + validateVolumeResizeWithSize(volume, currentSize, newSize, shrinkOk); + + /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */ + // We need to publish this event to usage_volume table + if (volume.getState() == Volume.State.Allocated) { + s_logger.debug(String.format("Volume %s is in the allocated state, but has never been created. Simply updating database with new size and IOPS.", volume.getUuid())); + + volume.setSize(newSize); + volume.setMinIops(newMinIops); + volume.setMaxIops(newMaxIops); + volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); + + if (newDiskOffering != null) { + volume.setDiskOfferingId(newDiskOfferingId); + } + + _volsDao.update(volume.getId(), volume); + if (currentSize != newSize) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_RESIZE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), + volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); + } + return volume; + } + + if (currentSize != newSize || newMaxIops != volume.getMaxIops() || newMinIops != volume.getMinIops()) { + volumeResizeRequired = true; + validateVolumeReadyStateAndHypervisorChecks(volume, currentSize, newSize); + } + + StoragePoolVO existingStoragePool = _storagePoolDao.findById(volume.getPoolId()); + + Pair, List> poolsPair = managementService.listStoragePoolsForMigrationOfVolumeInternal(volume.getId(), newDiskOffering.getId(), newSize, newMinIops, newMaxIops, true); + List suitableStoragePools = poolsPair.second(); + + if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) { + volumeMigrateRequired = true; + if (!autoMigrateVolume) { + throw new InvalidParameterValueException(String.format("Failed to change offering for volume %s since automigrate is set to false but volume needs to migrated", volume.getUuid())); + } + } + + if (!volumeMigrateRequired && !volumeResizeRequired) { + _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); + volume = _volsDao.findById(volume.getId()); + return volume; + } + + if (volumeMigrateRequired) { + if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume ID: %s as no suitable pool(s) found for migrating to support new disk offering", volume.getUuid())); + } + Collections.shuffle(suitableStoragePools); + MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePools.get(0).getId(), newDiskOffering.getId(), true); + try { + volume = (VolumeVO) migrateVolume(migrateVolumeCmd); + if (volume == null) { + throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume ID: %s migration failed to storage pool %s", volume.getUuid(), suitableStoragePools.get(0).getId())); + } + } catch (Exception e) { + throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume ID: %s migration failed to storage pool %s due to %s", volume.getUuid(), suitableStoragePools.get(0).getId(), e.getMessage())); + } + } + + if (volumeResizeRequired) { + // refresh volume data + volume = _volsDao.findById(volume.getId()); + try { + volume = resizeVolumeInternal(volume, newDiskOffering, currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk); + } catch (Exception e) { + if (volumeMigrateRequired) { + s_logger.warn(String.format("Volume change offering operation succeeded for volume ID: %s but volume resize operation failed, so please try resize volume operation separately", volume.getUuid())); + } else { + throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume ID: %s due to resize volume operation failed", volume.getUuid())); + } + } + } + + return volume; + } + + private VolumeVO resizeVolumeInternal(VolumeVO volume, DiskOfferingVO newDiskOffering, Long currentSize, Long newSize, Long newMinIops, Long newMaxIops, Integer newHypervisorSnapshotReserve, boolean shrinkOk) throws ResourceAllocationException { + UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); + HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId()); + + if (userVm != null) { + if (volume.getVolumeType().equals(Volume.Type.ROOT) && userVm.getPowerState() != VirtualMachine.PowerState.PowerOff && hypervisorType == HypervisorType.VMware) { + s_logger.error(" For ROOT volume resize VM should be in Power Off state."); + throw new InvalidParameterValueException("VM current state is : " + userVm.getPowerState() + ". But VM should be in " + VirtualMachine.PowerState.PowerOff + " state."); + } + // serialize VM operation + AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + // avoid re-entrance + + VmWorkJobVO placeHolder = null; + + placeHolder = createPlaceHolderWork(userVm.getId()); + + try { + return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, + newDiskOffering != null ? newDiskOffering.getId() : null, shrinkOk); + } finally { + _workJobDao.expunge(placeHolder.getId()); + } + } else { + Outcome outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, + newDiskOffering != null ? newDiskOffering.getId() : null, shrinkOk); + + try { + outcome.get(); + } catch (InterruptedException e) { + throw new RuntimeException("Operation was interrupted", e); + } catch (java.util.concurrent.ExecutionException e) { + throw new RuntimeException("Execution exception", e); + } + + Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); + + if (jobResult != null) { + if (jobResult instanceof ConcurrentOperationException) { + throw (ConcurrentOperationException)jobResult; + } else if (jobResult instanceof ResourceAllocationException) { + throw (ResourceAllocationException)jobResult; + } else if (jobResult instanceof RuntimeException) { + throw (RuntimeException)jobResult; + } else if (jobResult instanceof Throwable) { + throw new RuntimeException("Unexpected exception", (Throwable)jobResult); + } else if (jobResult instanceof Long) { + return _volsDao.findById((Long)jobResult); + } + } + + return volume; + } + } + + return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? newDiskOffering.getId() : null, + shrinkOk); + } + + private void validateVolumeReadyStateAndHypervisorChecks(VolumeVO volume, long currentSize, Long newSize) { + // checking if there are any ongoing snapshots on the volume which is to be resized + List ongoingSnapshots = _snapshotDao.listByStatus(volume.getId(), Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp); + if (ongoingSnapshots.size() > 0) { + throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on this volume, resize volume is not permitted, please try again later."); + } + + /* Only works for KVM/XenServer/VMware (or "Any") for now, and volumes with 'None' since they're just allocated in DB */ + HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId()); + + if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer + && hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any + && hypervisorType != HypervisorType.None) { + throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support volume resize"); + } + + if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) { + throw new InvalidParameterValueException("Volume should be in ready or allocated state before attempting a resize. Volume " + volume.getUuid() + " is in state " + volume.getState() + "."); + } + + if (hypervisorType.equals(HypervisorType.VMware) && newSize < currentSize) { + throw new InvalidParameterValueException("VMware doesn't support shrinking volume from larger size: " + currentSize + " GB to a smaller size: " + newSize + " GB"); + } + + UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); + if (userVm != null) { + if (volume.getVolumeType().equals(Volume.Type.ROOT) && userVm.getPowerState() != VirtualMachine.PowerState.PowerOff && hypervisorType == HypervisorType.VMware) { + s_logger.error(" For ROOT volume resize VM should be in Power Off state."); + throw new InvalidParameterValueException("VM current state is : " + userVm.getPowerState() + ". But VM should be in " + VirtualMachine.PowerState.PowerOff + " state."); + } + } + } + + private void validateVolumeResizeWithNewDiskOfferingAndLoad(VolumeVO volume, DiskOfferingVO existingDiskOffering, DiskOfferingVO newDiskOffering, Long[] newSize, Long[] newMinIops, Long[] newMaxIops, Integer[] newHypervisorSnapshotReserve) { + if (newDiskOffering.getRemoved() != null) { + throw new InvalidParameterValueException("Requested disk offering has been removed."); + } + + if (newDiskOffering.getId() == existingDiskOffering.getId()) { + throw new InvalidParameterValueException(String.format("Volume %s already have the new disk offering %s provided", volume.getUuid(), existingDiskOffering.getUuid())); + } + + if (existingDiskOffering.getDiskSizeStrictness() != newDiskOffering.getDiskSizeStrictness()) { + throw new InvalidParameterValueException("Disk offering size strictness does not match with new disk offering"); + } + + if (MatchStoragePoolTagsWithDiskOffering.valueIn(volume.getDataCenterId())) { + if (!doesNewDiskOfferingHasTagsAsOldDiskOffering(existingDiskOffering, newDiskOffering)) { + throw new InvalidParameterValueException(String.format("Selected disk offering %s does not have tags as in existing disk offering of volume %s", existingDiskOffering.getUuid(), volume.getUuid())); + } + } + + Long instanceId = volume.getInstanceId(); + VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId); + if (volume.getVolumeType().equals(Volume.Type.ROOT)) { + ServiceOfferingVO serviceOffering = _serviceOfferingDao.findById(vmInstanceVO.getServiceOfferingId()); + if (serviceOffering != null && serviceOffering.getDiskOfferingStrictness()) { + throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] with new disk offering since existing disk offering is strictly assigned to the ROOT volume.", volume.getName())); + } + } + + _configMgr.checkDiskOfferingAccess(_accountMgr.getActiveAccountById(volume.getAccountId()), newDiskOffering, _dcDao.findById(volume.getDataCenterId())); + + if (newDiskOffering.getDiskSize() > 0 && !newDiskOffering.isComputeOnly()) { + newSize[0] = (Long) newDiskOffering.getDiskSize(); + } else if (newDiskOffering.isCustomized()) { + if (newSize[0] == null) { + throw new InvalidParameterValueException("The new disk offering requires that a size be specified."); + } + + // convert from GiB to bytes + newSize[0] = newSize[0] << 30; + } else { + if (newSize[0] != null) { + throw new InvalidParameterValueException("You cannot pass in a custom disk size to a non-custom disk offering."); + } + + if (newDiskOffering.isComputeOnly() && newDiskOffering.getDiskSize() == 0) { + newSize[0] = volume.getSize(); + } else { + newSize[0] = newDiskOffering.getDiskSize(); + } + if (newDiskOffering.isCustomizedIops() != null && newDiskOffering.isCustomizedIops()) { + newMinIops[0] = newMinIops[0] != null ? newMinIops[0] : volume.getMinIops(); + newMaxIops[0] = newMaxIops[0] != null ? newMaxIops[0] : volume.getMaxIops(); + + validateIops(newMinIops[0], newMaxIops[0], volume.getPoolType()); + } else { + newMinIops[0] = newDiskOffering.getMinIops(); + newMaxIops[0] = newDiskOffering.getMaxIops(); + } + + // if the hypervisor snapshot reserve value is null, it must remain null (currently only KVM uses null and null is all KVM uses for a value here) + newHypervisorSnapshotReserve[0] = volume.getHypervisorSnapshotReserve() != null ? newDiskOffering.getHypervisorSnapshotReserve() : null; + } + + if (existingDiskOffering.getDiskSizeStrictness() && !(volume.getSize().equals(newSize[0]))) { + throw new InvalidParameterValueException(String.format("Resize volume for %s is not allowed since disk offering's size is fixed", volume.getName())); + } + checkIfVolumeIsRootAndVmIsRunning(newSize[0], volume, vmInstanceVO); + + } + + private void validateVolumeResizeWithSize(VolumeVO volume, long currentSize, Long newSize, boolean shrinkOk) throws ResourceAllocationException { + + // if the caller is looking to change the size of the volume + if (currentSize != newSize) { + if (volume.getInstanceId() != null) { + // Check that VM to which this volume is attached does not have VM snapshots + if (_vmSnapshotDao.findByVm(volume.getInstanceId()).size() > 0) { + throw new InvalidParameterValueException("A volume that is attached to a VM with any VM snapshots cannot be resized."); + } + } + + if (!validateVolumeSizeRange(newSize)) { + throw new InvalidParameterValueException("Requested size out of range"); + } + + Long storagePoolId = volume.getPoolId(); + + if (storagePoolId != null) { + StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId); + + if (storagePoolVO.isManaged()) { + Long instanceId = volume.getInstanceId(); + + if (instanceId != null) { + VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId); + + if (vmInstanceVO.getHypervisorType() == HypervisorType.KVM && vmInstanceVO.getState() != State.Stopped) { + throw new CloudRuntimeException("This kind of KVM disk cannot be resized while it is connected to a VM that's not in the Stopped state."); + } + } + } + } + + /* + * Let's make certain they (think they) know what they're doing if they + * want to shrink by forcing them to provide the shrinkok parameter. + * This will be checked again at the hypervisor level where we can see + * the actual disk size. + */ + if (currentSize > newSize) { + if (volume != null && ImageFormat.QCOW2.equals(volume.getFormat()) && !Volume.State.Allocated.equals(volume.getState())) { + String message = "Unable to shrink volumes of type QCOW2"; + s_logger.warn(message); + throw new InvalidParameterValueException(message); + } + } + if (currentSize > newSize && !shrinkOk) { + throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." + + "Need to sign off by supplying the shrinkok parameter with value of true."); + } + + if (newSize > currentSize) { + /* Check resource limit for this account on primary storage resource */ + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), + new Long(newSize - currentSize).longValue()); + } + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true) public Volume attachVolumeToVM(AttachVolumeCmd command) { @@ -2396,6 +2774,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Cannot migrate volume " + vol + "to the destination storage pool " + destPool.getName() + " as the storage pool is in maintenance mode."); } + DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); + if (diskOffering == null) { + throw new CloudRuntimeException("volume '" + vol.getUuid() + "', has no diskoffering. Migration target cannot be checked."); + } String poolUuid = destPool.getUuid(); if (destPool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) { DataCenter dc = _entityMgr.findById(DataCenter.class, vol.getDataCenterId()); @@ -2408,16 +2790,15 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new CloudRuntimeException("Storage pool " + destPool.getName() + " is not suitable to migrate volume " + vol.getName()); } - if (!storageMgr.storagePoolHasEnoughSpace(Collections.singletonList(vol), destPool)) { + HypervisorType hypervisorType = _volsDao.getHypervisorType(volumeId); + DiskProfile diskProfile = new DiskProfile(vol, diskOffering, hypervisorType); + Pair volumeDiskProfilePair = new Pair<>(vol, diskProfile); + if (!storageMgr.storagePoolHasEnoughSpace(Collections.singletonList(volumeDiskProfilePair), destPool)) { throw new CloudRuntimeException("Storage pool " + destPool.getName() + " does not have enough space to migrate volume " + vol.getName()); } // OfflineVmwareMigration: check storage tags on disk(offering)s in comparison to destination storage pool // OfflineVmwareMigration: if no match return a proper error now - DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); - if (diskOffering == null) { - throw new CloudRuntimeException("volume '" + vol.getUuid() + "', has no diskoffering. Migration target cannot be checked."); - } if (liveMigrateVolume && State.Running.equals(vm.getState()) && destPool.getClusterId() != null && srcClusterId != null) { @@ -2444,10 +2825,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } - HypervisorType hypervisorType = _volsDao.getHypervisorType(volumeId); if (hypervisorType.equals(HypervisorType.VMware)) { try { - boolean isStoragePoolStoragepolicyComplaince = storageMgr.isStoragePoolCompliantWithStoragePolicy(Arrays.asList(vol), destPool); + boolean isStoragePoolStoragepolicyComplaince = storageMgr.isStoragePoolCompliantWithStoragePolicy(Arrays.asList(volumeDiskProfilePair), destPool); if (!isStoragePoolStoragepolicyComplaince) { throw new CloudRuntimeException(String.format("Storage pool %s is not storage policy compliance with the volume %s", poolUuid, vol.getUuid())); } @@ -2458,6 +2838,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic DiskOfferingVO newDiskOffering = retrieveAndValidateNewDiskOffering(cmd); validateConditionsToReplaceDiskOfferingOfVolume(vol, newDiskOffering, destPool); + if (vm != null) { // serialize VM operation AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); @@ -2517,13 +2898,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic * If all checks pass, we move forward returning the disk offering object. */ private DiskOfferingVO retrieveAndValidateNewDiskOffering(MigrateVolumeCmd cmd) { - String newDiskOfferingUuid = cmd.getNewDiskOfferingUuid(); - if (StringUtils.isBlank(newDiskOfferingUuid)) { + Long newDiskOfferingId = cmd.getNewDiskOfferingId(); + if (newDiskOfferingId == null) { return null; } - DiskOfferingVO newDiskOffering = _diskOfferingDao.findByUuid(newDiskOfferingUuid); + DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newDiskOfferingId); if (newDiskOffering == null) { - throw new InvalidParameterValueException(String.format("The disk offering informed is not valid [id=%s].", newDiskOfferingUuid)); + throw new InvalidParameterValueException(String.format("The disk offering informed is not valid [id=%s].", newDiskOfferingId)); } if (newDiskOffering.getRemoved() != null) { throw new InvalidParameterValueException(String.format("We cannot assign a removed disk offering [id=%s] to a volume. ", newDiskOffering.getUuid())); @@ -2535,6 +2916,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic zone = _dcDao.findById(volume.getDataCenterId()); } _accountMgr.checkAccess(caller, newDiskOffering, zone); + DiskOfferingVO currentDiskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + if (VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(zone.getId()) && !doesNewDiskOfferingHasTagsAsOldDiskOffering(currentDiskOffering, newDiskOffering)) { + throw new InvalidParameterValueException(String.format("Existing disk offering storage tags of the volume %s does not contain in the new disk offering %s ", volume.getUuid(), newDiskOffering.getUuid())); + } return newDiskOffering; } @@ -2554,12 +2939,25 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return; } if ((destPool.isShared() && newDiskOffering.isUseLocalStorage()) || destPool.isLocal() && newDiskOffering.isShared()) { - throw new InvalidParameterValueException("You cannot move the volume to a shared storage and assing a disk offering for local storage and vice versa."); + throw new InvalidParameterValueException("You cannot move the volume to a shared storage and assign a disk offering for local storage and vice versa."); } if (!doesTargetStorageSupportDiskOffering(destPool, newDiskOffering)) { - throw new InvalidParameterValueException(String.format("Target Storage [id=%s] tags [%s] does not match new disk offering [id=%s] tags [%s].", destPool.getUuid(), - getStoragePoolTags(destPool), newDiskOffering.getUuid(), newDiskOffering.getTags())); + throw new InvalidParameterValueException(String.format("Migration failed: target pool [%s, tags:%s] has no matching tags for volume [%s, uuid:%s, tags:%s]", destPool.getName(), + getStoragePoolTags(destPool), volume.getName(), volume.getUuid(), newDiskOffering.getTags())); } + if (volume.getVolumeType().equals(Volume.Type.ROOT)) { + VMInstanceVO vm = null; + if (volume.getInstanceId() != null) { + vm = _vmInstanceDao.findById(volume.getInstanceId()); + } + if (vm != null) { + ServiceOfferingVO serviceOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); + if (serviceOffering != null && serviceOffering.getDiskOfferingStrictness()) { + throw new InvalidParameterValueException(String.format("Disk offering cannot be changed to the volume %s since existing disk offering is strictly associated with the volume", volume.getUuid())); + } + } + } + if (volume.getSize() != newDiskOffering.getDiskSize()) { DiskOfferingVO oldDiskOffering = this._diskOfferingDao.findById(volume.getDiskOfferingId()); s_logger.warn(String.format( @@ -2621,6 +3019,18 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return CollectionUtils.isSubCollection(Arrays.asList(newDiskOfferingTagsAsStringArray), Arrays.asList(storageTagsAsStringArray)); } + public boolean doesNewDiskOfferingHasTagsAsOldDiskOffering(DiskOfferingVO oldDO, DiskOfferingVO newDO) { + String[] oldDOStorageTags = oldDO.getTagsArray(); + String[] newDOStorageTags = newDO.getTagsArray(); + if (oldDOStorageTags.length == 0) { + return true; + } + if (newDOStorageTags.length == 0) { + return false; + } + return CollectionUtils.isSubCollection(Arrays.asList(oldDOStorageTags), Arrays.asList(newDOStorageTags)); + } + /** * Retrieves the storage pool tags as a {@link String}. If the storage pool does not have tags we return a null value. */ @@ -3651,7 +4061,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } public Outcome resizeVolumeThroughJobQueue(final Long vmId, final long volumeId, final long currentSize, final long newSize, final Long newMinIops, final Long newMaxIops, - final Integer newHypervisorSnapshotReserve, final Long newServiceOfferingId, final boolean shrinkOk) { + final Integer newHypervisorSnapshotReserve, final Long newServiceOfferingId, final boolean shrinkOk) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); @@ -3745,7 +4155,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } public Outcome takeVolumeSnapshotThroughJobQueue(final Long vmId, final Long volumeId, final Long policyId, final Long snapshotId, final Long accountId, final boolean quiesceVm, - final Snapshot.LocationType locationType, final boolean asyncBackup) { + final Snapshot.LocationType locationType, final boolean asyncBackup) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); @@ -3852,6 +4262,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {ConcurrentMigrationsThresholdPerDatastore, AllowUserExpungeRecoverVolume}; + return new ConfigKey[] {ConcurrentMigrationsThresholdPerDatastore, AllowUserExpungeRecoverVolume, MatchStoragePoolTagsWithDiskOffering}; } } diff --git a/server/src/main/java/com/cloud/test/DatabaseConfig.java b/server/src/main/java/com/cloud/test/DatabaseConfig.java index 4e0cbac6735..0dda96b3893 100644 --- a/server/src/main/java/com/cloud/test/DatabaseConfig.java +++ b/server/src/main/java/com/cloud/test/DatabaseConfig.java @@ -932,30 +932,38 @@ public class DatabaseConfig { } else { useLocalStorage = false; } + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(name, displayText, provisioningType, false, null, false, false, true); ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, - provisioningType, useLocalStorage, false, null, false, null, false); + false, null, false); Long bytesReadRate = Long.parseLong(_currentObjectParams.get("bytesReadRate")); if ((bytesReadRate != null) && (bytesReadRate > 0)) - serviceOffering.setBytesReadRate(bytesReadRate); + diskOfferingVO.setBytesReadRate(bytesReadRate); Long bytesWriteRate = Long.parseLong(_currentObjectParams.get("bytesWriteRate")); if ((bytesWriteRate != null) && (bytesWriteRate > 0)) - serviceOffering.setBytesWriteRate(bytesWriteRate); + diskOfferingVO.setBytesWriteRate(bytesWriteRate); Long iopsReadRate = Long.parseLong(_currentObjectParams.get("iopsReadRate")); if ((iopsReadRate != null) && (iopsReadRate > 0)) - serviceOffering.setIopsReadRate(iopsReadRate); + diskOfferingVO.setIopsReadRate(iopsReadRate); Long iopsWriteRate = Long.parseLong(_currentObjectParams.get("iopsWriteRate")); if ((iopsWriteRate != null) && (iopsWriteRate > 0)) - serviceOffering.setIopsWriteRate(iopsWriteRate); + diskOfferingVO.setIopsWriteRate(iopsWriteRate); - ServiceOfferingDaoImpl dao = ComponentContext.inject(ServiceOfferingDaoImpl.class); + DiskOfferingDaoImpl DiskOfferinDao = ComponentContext.inject(DiskOfferingDaoImpl.class); try { - dao.persist(serviceOffering); + DiskOfferinDao.persist(diskOfferingVO); + } catch (Exception e) { + s_logger.error("error creating disk offering", e); + } + + serviceOffering.setDiskOfferingId(diskOfferingVO.getId()); + ServiceOfferingDaoImpl serviceOfferingDao = ComponentContext.inject(ServiceOfferingDaoImpl.class); + try { + serviceOfferingDao.persist(serviceOffering); } catch (Exception e) { s_logger.error("error creating service offering", e); - } /* String insertSql = "INSERT INTO `cloud`.`service_offering` (id, name, cpu, ram_size, speed, nw_rate, mc_rate, created, ha_enabled, mirrored, display_text, guest_ip_type, use_local_storage) " + diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 76da7e5d250..275f7018b40 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -84,6 +84,7 @@ import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; +import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; @@ -1115,57 +1116,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir _accountMgr.checkAccess(caller, null, true, vmInstance); - // Check resource limits for CPU and Memory. - Map customParameters = cmd.getDetails(); - ServiceOfferingVO newServiceOffering = _offeringDao.findById(svcOffId); - if (newServiceOffering.getState() == DiskOffering.State.Inactive) { - throw new InvalidParameterValueException(String.format("Unable to upgrade virtual machine %s with an inactive service offering %s", vmInstance.getUuid(), newServiceOffering.getUuid())); - } - if (newServiceOffering.isDynamic()) { - newServiceOffering.setDynamicFlag(true); - validateCustomParameters(newServiceOffering, cmd.getDetails()); - newServiceOffering = _offeringDao.getComputeOffering(newServiceOffering, customParameters); - } else { - validateOfferingMaxResource(newServiceOffering); - } - - ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); - - int newCpu = newServiceOffering.getCpu(); - int newMemory = newServiceOffering.getRamSize(); - int currentCpu = currentServiceOffering.getCpu(); - int currentMemory = currentServiceOffering.getRamSize(); - - Account owner = _accountMgr.getActiveAccountById(vmInstance.getAccountId()); - if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { - if (newCpu > currentCpu) { - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.cpu, newCpu - currentCpu); - } - if (newMemory > currentMemory) { - _resourceLimitMgr.checkResourceLimit(owner, ResourceType.memory, newMemory - currentMemory); - } - } - - // Check that the specified service offering ID is valid - _itMgr.checkIfCanUpgrade(vmInstance, newServiceOffering); - - resizeRootVolumeOfVmWithNewOffering(vmInstance, newServiceOffering); - - _itMgr.upgradeVmDb(vmId, newServiceOffering, currentServiceOffering); - - // Increment or decrement CPU and Memory count accordingly. - if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { - if (newCpu > currentCpu) { - _resourceLimitMgr.incrementResourceCount(owner.getAccountId(), ResourceType.cpu, new Long(newCpu - currentCpu)); - } else if (currentCpu > newCpu) { - _resourceLimitMgr.decrementResourceCount(owner.getAccountId(), ResourceType.cpu, new Long(currentCpu - newCpu)); - } - if (newMemory > currentMemory) { - _resourceLimitMgr.incrementResourceCount(owner.getAccountId(), ResourceType.memory, new Long(newMemory - currentMemory)); - } else if (currentMemory > newMemory) { - _resourceLimitMgr.decrementResourceCount(owner.getAccountId(), ResourceType.memory, new Long(currentMemory - newMemory)); - } - } + upgradeStoppedVirtualMachine(vmId, svcOffId, cmd.getDetails()); // Generate usage event for VM upgrade UserVmVO userVm = _vmDao.findById(vmId); @@ -1230,19 +1181,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } private UserVm upgradeStoppedVirtualMachine(Long vmId, Long svcOffId, Map customParameters) throws ResourceAllocationException { - Account caller = CallContext.current().getCallingAccount(); - // Verify input parameters - //UserVmVO vmInstance = _vmDao.findById(vmId); VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId); - if (vmInstance == null) { - throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); - } - - _accountMgr.checkAccess(caller, null, true, vmInstance); - // Check resource limits for CPU and Memory. ServiceOfferingVO newServiceOffering = _offeringDao.findById(svcOffId); + if (newServiceOffering.getState() == ServiceOffering.State.Inactive) { + throw new InvalidParameterValueException(String.format("Unable to upgrade virtual machine %s with an inactive service offering %s", vmInstance.getUuid(), newServiceOffering.getUuid())); + } if (newServiceOffering.isDynamic()) { newServiceOffering.setDynamicFlag(true); validateCustomParameters(newServiceOffering, customParameters); @@ -1252,6 +1197,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); + validateDiskOfferingChecks(currentServiceOffering, newServiceOffering); + int newCpu = newServiceOffering.getCpu(); int newMemory = newServiceOffering.getRamSize(); int currentCpu = currentServiceOffering.getCpu(); @@ -1271,29 +1218,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir _itMgr.checkIfCanUpgrade(vmInstance, newServiceOffering); // Check if the new service offering can be applied to vm instance - ServiceOffering newSvcOffering = _offeringDao.findById(svcOffId); - _accountMgr.checkAccess(owner, newSvcOffering, _dcDao.findById(vmInstance.getDataCenterId())); + _accountMgr.checkAccess(owner, newServiceOffering, _dcDao.findById(vmInstance.getDataCenterId())); - DiskOfferingVO newRootDiskOffering = _diskOfferingDao.findById(newServiceOffering.getId()); - - List vols = _volsDao.findReadyAndAllocatedRootVolumesByInstance(vmInstance.getId()); - - for (final VolumeVO rootVolumeOfVm : vols) { - DiskOfferingVO currentRootDiskOffering = _diskOfferingDao.findById(rootVolumeOfVm.getDiskOfferingId()); - - ResizeVolumeCmd resizeVolumeCmd = prepareResizeVolumeCmd(rootVolumeOfVm, currentRootDiskOffering, newRootDiskOffering); - - if (rootVolumeOfVm.getDiskOfferingId() != newRootDiskOffering.getId()) { - rootVolumeOfVm.setDiskOfferingId(newRootDiskOffering.getId()); - _volsDao.update(rootVolumeOfVm.getId(), rootVolumeOfVm); - } - HypervisorType hypervisorType = _volsDao.getHypervisorType(rootVolumeOfVm.getId()); - if (HypervisorType.Simulator != hypervisorType) { - _volumeService.resizeVolume(resizeVolumeCmd); - } else if (newRootDiskOffering.getDiskSize() > 0 && currentRootDiskOffering.getDiskSize() != newRootDiskOffering.getDiskSize()) { - throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support volume resize"); - } - } + // resize and migrate the root volume if required + DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newServiceOffering.getDiskOfferingId()); + changeDiskOfferingForRootVolume(vmId, newDiskOffering, customParameters); _itMgr.upgradeVmDb(vmId, newServiceOffering, currentServiceOffering); @@ -1349,19 +1278,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return resizeVolumeCmd; } - private void resizeRootVolumeOfVmWithNewOffering(VMInstanceVO vmInstance, ServiceOfferingVO newServiceOffering) - throws ResourceAllocationException { - DiskOfferingVO newROOTDiskOffering = _diskOfferingDao.findById(newServiceOffering.getId()); - List vols = _volsDao.findReadyAndAllocatedRootVolumesByInstance(vmInstance.getId()); - - for (final VolumeVO rootVolumeOfVm : vols) { - rootVolumeOfVm.setDiskOfferingId(newROOTDiskOffering.getId()); - ResizeVolumeCmd resizeVolumeCmd = new ResizeVolumeCmd(rootVolumeOfVm.getId(), newROOTDiskOffering.getMinIops(), newROOTDiskOffering.getMaxIops()); - _volumeService.resizeVolume(resizeVolumeCmd); - _volsDao.update(rootVolumeOfVm.getId(), rootVolumeOfVm); - } - } - @Override @ActionEvent(eventType = EventTypes.EVENT_NIC_CREATE, eventDescription = "Creating Nic", async = true) public UserVm addNicToVirtualMachine(AddNicToVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException { @@ -1920,7 +1836,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Verify input parameters VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId); - + Account caller = CallContext.current().getCallingAccount(); + _accountMgr.checkAccess(caller, null, true, vmInstance); if (vmInstance != null) { if (vmInstance.getState().equals(State.Stopped)) { upgradeStoppedVirtualMachine(vmId, newServiceOfferingId, customParameters); @@ -1971,6 +1888,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException("Unable to Scale VM: since dynamic scaling enabled flag is not same for new service offering and old service offering"); } + validateDiskOfferingChecks(currentServiceOffering, newServiceOffering); + int newCpu = newServiceOffering.getCpu(); int newMemory = newServiceOffering.getRamSize(); int newSpeed = newServiceOffering.getSpeed(); @@ -2066,7 +1985,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir _itMgr.findHostAndMigrate(vmInstance.getUuid(), newServiceOfferingId, customParameters, excludes); } - // #3 scale the vm now + // #3 resize or migrate the root volume if required + DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newServiceOffering.getDiskOfferingId()); + changeDiskOfferingForRootVolume(vmId, newDiskOffering, customParameters); + + // #4 scale the vm now vmInstance = _vmInstanceDao.findById(vmId); _itMgr.reConfigureVm(vmInstance.getUuid(), currentServiceOffering, newServiceOffering, customParameters, existingHostHasCapacity); success = true; @@ -2090,6 +2013,51 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return success; } + private void validateDiskOfferingChecks(ServiceOfferingVO currentServiceOffering, ServiceOfferingVO newServiceOffering) { + if (currentServiceOffering.getDiskOfferingStrictness() != newServiceOffering.getDiskOfferingStrictness()) { + throw new InvalidParameterValueException("Unable to Scale VM, since disk offering strictness flag is not same for new service offering and old service offering"); + } + + if (currentServiceOffering.getDiskOfferingStrictness() && currentServiceOffering.getDiskOfferingId() != newServiceOffering.getDiskOfferingId()) { + throw new InvalidParameterValueException("Unable to Scale VM, since disk offering id associated with the old service offering is not same for new service offering"); + } + } + + private void changeDiskOfferingForRootVolume(Long vmId, DiskOfferingVO newDiskOffering, Map customParameters) throws ResourceAllocationException { + + List vols = _volsDao.findReadyAndAllocatedRootVolumesByInstance(vmId); + + for (final VolumeVO rootVolumeOfVm : vols) { + DiskOfferingVO currentRootDiskOffering = _diskOfferingDao.findById(rootVolumeOfVm.getDiskOfferingId()); + HypervisorType hypervisorType = _volsDao.getHypervisorType(rootVolumeOfVm.getId()); + if (HypervisorType.Simulator != hypervisorType) { + Long minIopsInNewDiskOffering = null; + Long maxIopsInNewDiskOffering = null; + boolean autoMigrate = false; + boolean shrinkOk = false; + if (customParameters.containsKey(ApiConstants.MIN_IOPS)) { + minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MIN_IOPS)); + } + if (customParameters.containsKey(ApiConstants.MAX_IOPS)) { + minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MAX_IOPS)); + } + if (customParameters.containsKey(ApiConstants.AUTO_MIGRATE)) { + autoMigrate = Boolean.parseBoolean(customParameters.get(ApiConstants.AUTO_MIGRATE)); + } + if (customParameters.containsKey(ApiConstants.SHRINK_OK)) { + shrinkOk = Boolean.parseBoolean(customParameters.get(ApiConstants.SHRINK_OK)); + } + ChangeOfferingForVolumeCmd changeOfferingForVolumeCmd = new ChangeOfferingForVolumeCmd(rootVolumeOfVm.getId(), newDiskOffering.getId(), minIopsInNewDiskOffering, maxIopsInNewDiskOffering, autoMigrate, shrinkOk); + Volume result = _volumeService.changeDiskOfferingForVolume(changeOfferingForVolumeCmd); + if (result == null) { + throw new CloudRuntimeException("Failed to change disk offering of the root volume"); + } + } else if (newDiskOffering.getDiskSize() > 0 && currentRootDiskOffering.getDiskSize() != newDiskOffering.getDiskSize()) { + throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support volume resize"); + } + } + } + @Override public HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds) throws CloudRuntimeException { HashMap vmStatsById = new HashMap(); @@ -2258,7 +2226,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Long offeringId = null; if (diskOfferingId != null) { DiskOfferingVO offering = _diskOfferingDao.findById(diskOfferingId); - if (offering != null && (offering.getType() == DiskOfferingVO.Type.Disk)) { + if (offering != null && !offering.isComputeOnly()) { offeringId = offering.getId(); } } @@ -3433,7 +3401,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParametes, String customId, Map> dhcpOptionMap, - Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3482,7 +3450,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, dhcpOptionMap, - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); } @@ -3492,7 +3460,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, - Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled) throws InsufficientCapacityException, ConcurrentOperationException, + Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3593,7 +3561,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - userVmOVFProperties, dynamicScalingEnabled, null); + userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); } @Override @@ -3602,7 +3570,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, Map customParametrs, String customId, Map> dhcpOptionsMap, Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3654,7 +3622,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, httpmethod, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, dhcpOptionsMap, - dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type); + dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type, overrideDiskOfferingId); } private NetworkVO getNetworkToAddToNetworkList(VirtualMachineTemplate template, Account owner, HypervisorType hypervisor, @@ -3773,7 +3741,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir String sshKeyPair, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, - Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type) throws InsufficientCapacityException, ResourceUnavailableException, + Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type, Long overrideDiskOfferingId) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException { _accountMgr.checkAccess(caller, null, true, owner); @@ -3834,23 +3802,28 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // check if account/domain is with in resource limits to create a new vm boolean isIso = Storage.ImageFormat.ISO == template.getFormat(); - long size = configureCustomRootDiskSize(customParameters, template, hypervisorType, offering); - - if (diskOfferingId != null) { - DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); - if (diskOffering != null && diskOffering.isCustomized()) { - if (diskSize == null) { - throw new InvalidParameterValueException("This disk offering requires a custom size specified"); + Long rootDiskOfferingId = offering.getDiskOfferingId(); + if (isIso) { + if (diskOfferingId == null) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(rootDiskOfferingId); + if (diskOffering.isComputeOnly()) { + throw new InvalidParameterValueException("Installing from ISO requires a disk offering to be specified for the root disk."); } - Long customDiskOfferingMaxSize = VolumeOrchestrationService.CustomDiskOfferingMaxSize.value(); - Long customDiskOfferingMinSize = VolumeOrchestrationService.CustomDiskOfferingMinSize.value(); - if ((diskSize < customDiskOfferingMinSize) || (diskSize > customDiskOfferingMaxSize)) { - throw new InvalidParameterValueException("VM Creation failed. Volume size: " + diskSize + "GB is out of allowed range. Max: " + customDiskOfferingMaxSize - + " Min:" + customDiskOfferingMinSize); - } - size += diskSize * GiB_TO_BYTES; + } else { + rootDiskOfferingId = diskOfferingId; + diskOfferingId = null; } - size += _diskOfferingDao.findById(diskOfferingId).getDiskSize(); + } + if (!offering.getDiskOfferingStrictness() && overrideDiskOfferingId != null) { + rootDiskOfferingId = overrideDiskOfferingId; + } + + DiskOfferingVO rootdiskOffering = _diskOfferingDao.findById(rootDiskOfferingId); + long size = configureCustomRootDiskSize(customParameters, template, hypervisorType, rootdiskOffering); + + if (!isIso && diskOfferingId != null) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); + size += verifyAndGetDiskSize(diskOffering, diskSize); } if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { resourceLimitCheck(owner, isDisplayVm, new Long(offering.getCpu()), new Long(offering.getRamSize())); @@ -4137,7 +4110,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir UserVmVO vm = commitUserVm(zone, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, caller, isDisplayVm, keyboard, accountId, userId, offering, isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dhcpOptionMap, - datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type); + datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type, rootDiskOfferingId); // Assign instance to the group try { @@ -4161,6 +4134,24 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return vm; } + private long verifyAndGetDiskSize(DiskOfferingVO diskOffering, Long diskSize) { + long size = 0l; + if (diskOffering != null && diskOffering.isCustomized() && !diskOffering.isComputeOnly()) { + if (diskSize == null) { + throw new InvalidParameterValueException("This disk offering requires a custom size specified"); + } + Long customDiskOfferingMaxSize = VolumeOrchestrationService.CustomDiskOfferingMaxSize.value(); + Long customDiskOfferingMinSize = VolumeOrchestrationService.CustomDiskOfferingMinSize.value(); + if ((diskSize < customDiskOfferingMinSize) || (diskSize > customDiskOfferingMaxSize)) { + throw new InvalidParameterValueException("VM Creation failed. Volume size: " + diskSize + "GB is out of allowed range. Max: " + customDiskOfferingMaxSize + + " Min:" + customDiskOfferingMinSize); + } + size += diskSize * GiB_TO_BYTES; + } + size += diskOffering.getDiskSize(); + return size; + } + @Override public boolean checkIfDynamicScalingCanBeEnabled(VirtualMachine vm, ServiceOffering offering, VirtualMachineTemplate template, Long zoneId) { boolean canEnableDynamicScaling = (vm != null ? vm.isDynamicallyScalable() : true) && offering.isDynamicScalingEnabled() && template.isDynamicallyScalable() && UserVmManager.EnableDynamicallyScaleVm.valueIn(zoneId); @@ -4175,10 +4166,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir * Configures the Root disk size via User`s custom parameters. * If the Service Offering has the Root Disk size field configured then the User`s root disk custom parameter is overwritten by the service offering. */ - protected long configureCustomRootDiskSize(Map customParameters, VMTemplateVO template, HypervisorType hypervisorType, ServiceOfferingVO serviceOffering) { + protected long configureCustomRootDiskSize(Map customParameters, VMTemplateVO template, HypervisorType hypervisorType, DiskOfferingVO rootDiskOffering) { verifyIfHypervisorSupportsRootdiskSizeOverride(hypervisorType); - DiskOfferingVO diskOffering = _diskOfferingDao.findById(serviceOffering.getId()); - long rootDiskSizeInBytes = diskOffering.getDiskSize(); + long rootDiskSizeInBytes = verifyAndGetDiskSize(rootDiskOffering, NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1)); if (rootDiskSizeInBytes > 0) { //if the size at DiskOffering is not zero then the Service Offering had it configured, it holds priority over the User custom size long rootDiskSizeInGiB = rootDiskSizeInBytes / GiB_TO_BYTES; customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, String.valueOf(rootDiskSizeInGiB)); @@ -4249,12 +4239,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final String sshPublicKey, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String type) throws InsufficientCapacityException { + final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId) throws InsufficientCapacityException { return Transaction.execute(new TransactionCallbackWithException() { @Override public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCapacityException { UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, template.getGuestOSId(), offering.isOfferHA(), - offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, hostName, diskOfferingId); + offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, hostName); vm.setUuid(uuidName); vm.setDynamicallyScalable(dynamicScalingEnabled); @@ -4370,15 +4360,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir computeTags.add(offering.getHostTag()); List rootDiskTags = new ArrayList(); - rootDiskTags.add(offering.getTags()); + DiskOfferingVO rootDiskOfferingVO = _diskOfferingDao.findById(rootDiskOfferingId); + rootDiskTags.add(rootDiskOfferingVO.getTags()); if (isIso) { _orchSrvc.createVirtualMachineFromScratch(vm.getUuid(), Long.toString(owner.getAccountId()), vm.getIsoId().toString(), hostName, displayName, hypervisorType.name(), guestOSCategory.getName(), offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, - networkNicMap, plan, extraDhcpOptionMap); + networkNicMap, plan, extraDhcpOptionMap, rootDiskOfferingId); } else { _orchSrvc.createVirtualMachine(vm.getUuid(), Long.toString(owner.getAccountId()), Long.toString(template.getId()), hostName, displayName, hypervisorType.name(), - offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap); + offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize, extraDhcpOptionMap, + dataDiskTemplateToDiskOfferingMap, diskOfferingId, rootDiskOfferingId); } if (s_logger.isDebugEnabled()) { @@ -4444,13 +4436,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final long accountId, final long userId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKey, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String type) throws InsufficientCapacityException { + Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId) throws InsufficientCapacityException { return commitUserVm(false, zone, null, null, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, caller, isDisplayVm, keyboard, accountId, userId, offering, isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - userVmOVFPropertiesMap, null, dynamicScalingEnabled, type); + userVmOVFPropertiesMap, null, dynamicScalingEnabled, type, rootDiskOfferingId); } public void validateRootDiskResize(final HypervisorType hypervisorType, Long rootDiskSize, VMTemplateVO templateVO, UserVmVO vm, final Map customParameters) throws InvalidParameterValueException @@ -5585,12 +5577,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } Long serviceOfferingId = cmd.getServiceOfferingId(); + Long overrideDiskOfferingId = cmd.getOverrideDiskOfferingId(); ServiceOffering serviceOffering = _entityMgr.findById(ServiceOffering.class, serviceOfferingId); if (serviceOffering == null) { throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId); } + if (serviceOffering.getDiskOfferingStrictness() && overrideDiskOfferingId != null) { + throw new InvalidParameterValueException(String.format("Cannot override disk offering id %d since provided service offering is strictly mapped to its disk offering", overrideDiskOfferingId)); + } + if (!serviceOffering.isDynamic()) { for(String detail: cmd.getDetails().keySet()) { if(detail.equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.equalsIgnoreCase(VmDetailConstants.MEMORY)) { @@ -5617,7 +5614,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } if (cmd.getDetails().get("rootdisksize") != null) { - throw new InvalidParameterValueException("Overriding root disk size isn't supported for VMs deployed from defploy as-is templates"); + throw new InvalidParameterValueException("Overriding root disk size isn't supported for VMs deployed from deploy as-is templates"); } // Bootmode and boottype are not supported on VMWare dpeloy-as-is templates (since 4.15) @@ -5633,11 +5630,15 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (diskOffering == null) { throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingId); } + if (diskOffering.isComputeOnly()) { + throw new InvalidParameterValueException(String.format("The disk offering id %d provided is directly mapped to a service offering, please provide an individual disk offering", diskOfferingId)); + } } if (!zone.isLocalStorageEnabled()) { - if (serviceOffering.isUseLocalStorage()) { - throw new InvalidParameterValueException("Zone is not configured to use local storage but service offering " + serviceOffering.getName() + " uses it"); + DiskOffering diskOfferingMappedInServiceOffering = _entityMgr.findById(DiskOffering.class, serviceOffering.getDiskOfferingId()); + if (diskOfferingMappedInServiceOffering.isUseLocalStorage()) { + throw new InvalidParameterValueException("Zone is not configured to use local storage but disk offering " + diskOfferingMappedInServiceOffering.getName() + " mapped in service offering uses it"); } if (diskOffering != null && diskOffering.isUseLocalStorage()) { throw new InvalidParameterValueException("Zone is not configured to use local storage but disk offering " + diskOffering.getName() + " uses it"); @@ -5680,14 +5681,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId, size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData , sshKeyPairName , cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); } } else { if (zone.isSecurityGroupEnabled()) { vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); } else { if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) { @@ -5695,7 +5696,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), - cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null); + cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); } } // check if this templateId has a child ISO @@ -6655,7 +6656,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId()); if (hypervisorType.equals(HypervisorType.VMware)) { try { - boolean isStoragePoolStoragepolicyCompliance = storageManager.isStoragePoolCompliantWithStoragePolicy(Arrays.asList(volume), pool); + DiskOffering diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + DiskProfile diskProfile = new DiskProfile(volume, diskOffering, _volsDao.getHypervisorType(volume.getId())); + Pair volumeDiskProfilePair = new Pair<>(volume, diskProfile); + boolean isStoragePoolStoragepolicyCompliance = storageManager.isStoragePoolCompliantWithStoragePolicy(Arrays.asList(volumeDiskProfilePair), pool); if (!isStoragePoolStoragepolicyCompliance) { throw new CloudRuntimeException(String.format("Storage pool %s is not storage policy compliance with the volume %s", pool.getUuid(), volume.getUuid())); } @@ -7829,7 +7833,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir null, null, userData, caller, isDisplayVm, keyboard, accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKey, null, id, instanceName, uuidName, hypervisorType, customParameters, - null, null, null, powerState, dynamicScalingEnabled, null); + null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId()); } @Override diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 62298110eae..3bba233aaf5 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -95,6 +95,7 @@ import com.cloud.serializer.GsonHelper; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSHypervisor; import com.cloud.storage.Snapshot; @@ -392,16 +393,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOffering.getTags()); } - private boolean storagePoolSupportsServiceOffering(StoragePool pool, ServiceOffering serviceOffering) { - if (pool == null) { - return false; - } - if (serviceOffering == null) { - return false; - } - return volumeApiService.doesTargetStorageSupportDiskOffering(pool, serviceOffering.getTags()); - } - private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedInstanceTO instance, ServiceOfferingVO serviceOffering, final Account owner, final DataCenter zone, final Map details) throws ServerApiException, PermissionDeniedException, ResourceAllocationException { if (instance == null) { @@ -560,9 +551,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (diskOffering != null && !migrateAllowed && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) { throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with storage pool: %s of unmanaged disk: %s", diskOffering.getUuid(), storagePool.getUuid(), disk.getDiskId())); } - if (serviceOffering != null && !migrateAllowed && !storagePoolSupportsServiceOffering(storagePool, serviceOffering)) { - throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with storage pool: %s of unmanaged disk: %s", serviceOffering.getUuid(), storagePool.getUuid(), disk.getDiskId())); - } } private void checkUnmanagedDiskAndOfferingForImport(List disks, final Map diskOfferingMap, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed) @@ -811,14 +799,11 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { continue; } boolean poolSupportsOfferings = storagePoolSupportsDiskOffering(diskProfileStoragePool.second(), dOffering); - if (poolSupportsOfferings && profile.getType() == Volume.Type.ROOT) { - poolSupportsOfferings = storagePoolSupportsServiceOffering(diskProfileStoragePool.second(), serviceOffering); - } if (poolSupportsOfferings) { continue; } LOGGER.debug(String.format("Volume %s needs to be migrated", volumeVO.getUuid())); - Pair, List> poolsPair = managementService.listStoragePoolsForMigrationOfVolume(profile.getVolumeId()); + Pair, List> poolsPair = managementService.listStoragePoolsForMigrationOfVolumeInternal(profile.getVolumeId(), null, null, null, null, false); if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) { cleanupFailedImportVM(vm); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during volume ID: %s migration as no suitable pool(s) found", userVm.getInstanceName(), volumeVO.getUuid())); @@ -828,9 +813,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (CollectionUtils.isNotEmpty(storagePools)) { for (StoragePool pool : storagePools) { if (diskProfileStoragePool.second().getId() != pool.getId() && - storagePoolSupportsDiskOffering(pool, dOffering) && - (!profile.getType().equals(Volume.Type.ROOT) || - profile.getType().equals(Volume.Type.ROOT) && storagePoolSupportsServiceOffering(pool, serviceOffering))) { + storagePoolSupportsDiskOffering(pool, dOffering) + ) { storagePool = pool; break; } @@ -841,9 +825,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { storagePools = poolsPair.first(); for (StoragePool pool : storagePools) { if (diskProfileStoragePool.second().getId() != pool.getId() && - storagePoolSupportsDiskOffering(pool, dOffering) && - (!profile.getType().equals(Volume.Type.ROOT) || - profile.getType().equals(Volume.Type.ROOT) && storagePoolSupportsServiceOffering(pool, serviceOffering))) { + storagePoolSupportsDiskOffering(pool, dOffering) + ) { storagePool = pool; break; } @@ -1021,7 +1004,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (details.containsKey("maxIops")) { maxIops = Long.parseLong(details.get("maxIops")); } - diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, serviceOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), (rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB), minIops, maxIops, template, owner, null)); for (UnmanagedInstanceTO.Disk disk : dataDisks) { diff --git a/server/src/main/java/org/cloud/network/router/deployment/RouterDeploymentDefinition.java b/server/src/main/java/org/cloud/network/router/deployment/RouterDeploymentDefinition.java index 24bc501ca73..6c344e3cb65 100644 --- a/server/src/main/java/org/cloud/network/router/deployment/RouterDeploymentDefinition.java +++ b/server/src/main/java/org/cloud/network/router/deployment/RouterDeploymentDefinition.java @@ -23,6 +23,8 @@ import java.util.Map; import com.cloud.network.dao.NetworkDetailVO; import com.cloud.network.dao.NetworkDetailsDao; import com.cloud.network.router.VirtualRouter; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.dao.DiskOfferingDao; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.log4j.Logger; @@ -89,6 +91,7 @@ public class RouterDeploymentDefinition { protected VirtualRouterProviderDao vrProviderDao; protected NetworkOfferingDao networkOfferingDao; protected ServiceOfferingDao serviceOfferingDao; + protected DiskOfferingDao diskOfferingDao; protected IpAddressManager ipAddrMgr; protected VMInstanceDao vmDao; protected HostPodDao podDao; @@ -405,8 +408,9 @@ public class RouterDeploymentDefinition { logger.debug("Verifying router service offering with uuid : " + offeringUuid); ServiceOfferingVO serviceOffering = serviceOfferingDao.findByUuid(offeringUuid); if (serviceOffering != null && serviceOffering.isSystemUse()) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); boolean isLocalStorage = ConfigurationManagerImpl.SystemVMUseLocalStorage.valueIn(dest.getDataCenter().getId()); - if (isLocalStorage == serviceOffering.isUseLocalStorage()) { + if (isLocalStorage == diskOffering.isUseLocalStorage()) { logger.debug(String.format("Service offering %s (uuid: %s) will be used on virtual router", serviceOffering.getName(), serviceOffering.getUuid())); serviceOfferingId = serviceOffering.getId(); } diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java index 97e15de6f84..87266883d90 100644 --- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java @@ -98,7 +98,6 @@ import com.cloud.resource.ResourceManager; import com.cloud.org.Grouping.AllocationState; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDetailsDao; -import com.cloud.storage.Storage.ProvisioningType; import com.cloud.storage.StorageManager; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -213,8 +212,7 @@ public class DeploymentPlanningManagerImplTest { public void dataCenterAvoidTest() throws InsufficientServerCapacityException, AffinityConflictException { ServiceOfferingVO svcOffering = new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm", - ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, - null, "FirstFitPlanner", true); + false, VirtualMachine.Type.User, null, "FirstFitPlanner", true, false); Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering); DataCenterDeployment plan = new DataCenterDeployment(dataCenterId); @@ -228,8 +226,7 @@ public class DeploymentPlanningManagerImplTest { public void plannerCannotHandleTest() throws InsufficientServerCapacityException, AffinityConflictException { ServiceOfferingVO svcOffering = new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm", - ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, - null, "UserDispersingPlanner", true); + false, VirtualMachine.Type.User, null, "UserDispersingPlanner", true, false); Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering); DataCenterDeployment plan = new DataCenterDeployment(dataCenterId); @@ -244,8 +241,7 @@ public class DeploymentPlanningManagerImplTest { public void emptyClusterListTest() throws InsufficientServerCapacityException, AffinityConflictException { ServiceOfferingVO svcOffering = new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm", - ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, - null, "FirstFitPlanner", true); + false, VirtualMachine.Type.User, null, "FirstFitPlanner", true, false); Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering); DataCenterDeployment plan = new DataCenterDeployment(dataCenterId); diff --git a/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java b/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java index 1b5343881b8..66898942d76 100644 --- a/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java +++ b/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java @@ -95,7 +95,6 @@ import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.resource.ResourceManager; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.Storage.ProvisioningType; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.VMTemplateDao; @@ -336,10 +335,6 @@ public class VirtualRouterElementTest { /* multicastRateMbps */ 0, /* offerHA */ false, "displayText", - ProvisioningType.THIN, - /* useLocalStorage */ false, - /* recreatable */ false, - "tags", /* systemUse */ false, VirtualMachine.Type.DomainRouter, /* defaultUse */ false); diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index f317ea8acf3..dac091fbbb3 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -37,6 +37,8 @@ import java.util.concurrent.ExecutionException; import com.cloud.api.query.dao.ServiceOfferingJoinDao; import com.cloud.api.query.vo.ServiceOfferingJoinVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.dao.VMTemplateDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -158,6 +160,8 @@ public class VolumeApiServiceImplTest { private VMTemplateDao templateDao; @Mock private ServiceOfferingJoinDao serviceOfferingJoinDao; + @Mock + private ServiceOfferingDao serviceOfferingDao; private DetachVolumeCmd detachCmd = new DetachVolumeCmd(); private Class _detachCmdClass = detachCmd.getClass(); @@ -213,7 +217,7 @@ public class VolumeApiServiceImplTest { VolumeVO volumeOfRunningVm = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", "root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT); when(volumeDaoMock.findById(1L)).thenReturn(volumeOfRunningVm); - UserVmVO runningVm = new UserVmVO(1L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false, false, 1L, 1L, 1, 1L, null, "vm", null); + UserVmVO runningVm = new UserVmVO(1L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false, false, 1L, 1L, 1, 1L, null, "vm"); runningVm.setState(State.Running); runningVm.setDataCenterId(1L); when(userVmDaoMock.findById(1L)).thenReturn(runningVm); @@ -223,13 +227,13 @@ public class VolumeApiServiceImplTest { volumeOfStoppedVm.setPoolId(1L); when(volumeDaoMock.findById(2L)).thenReturn(volumeOfStoppedVm); - UserVmVO stoppedVm = new UserVmVO(2L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false, false, 1L, 1L, 1, 1L, null, "vm", null); + UserVmVO stoppedVm = new UserVmVO(2L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false, false, 1L, 1L, 1, 1L, null, "vm"); stoppedVm.setState(State.Stopped); stoppedVm.setDataCenterId(1L); when(userVmDaoMock.findById(2L)).thenReturn(stoppedVm); // volume of hyperV vm id=3 - UserVmVO hyperVVm = new UserVmVO(3L, "vm", "vm", 1, HypervisorType.Hyperv, 1L, false, false, 1L, 1L, 1, 1L, null, "vm", null); + UserVmVO hyperVVm = new UserVmVO(3L, "vm", "vm", 1, HypervisorType.Hyperv, 1L, false, false, 1L, 1L, 1, 1L, null, "vm"); hyperVVm.setState(State.Stopped); hyperVVm.setDataCenterId(1L); when(userVmDaoMock.findById(3L)).thenReturn(hyperVVm); @@ -285,7 +289,7 @@ public class VolumeApiServiceImplTest { when(volumeDaoMock.findById(7L)).thenReturn(managedVolume1); // vm having root volume - UserVmVO vmHavingRootVolume = new UserVmVO(4L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false, false, 1L, 1L, 1, 1L, null, "vm", null); + UserVmVO vmHavingRootVolume = new UserVmVO(4L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false, false, 1L, 1L, 1, 1L, null, "vm"); vmHavingRootVolume.setState(State.Stopped); vmHavingRootVolume.setDataCenterId(1L); when(userVmDaoMock.findById(4L)).thenReturn(vmHavingRootVolume); @@ -570,6 +574,27 @@ public class VolumeApiServiceImplTest { @Test public void validateConditionsToReplaceDiskOfferingOfVolumeTestRootVolume() { Mockito.lenient().when(volumeVoMock.getVolumeType()).thenReturn(Type.ROOT); + Mockito.doReturn(vmInstanceMockId).when(volumeVoMock).getInstanceId(); + UserVmVO vm = Mockito.mock(UserVmVO.class); + when(_vmInstanceDao.findById(anyLong())).thenReturn(vm); + when(vm.getServiceOfferingId()).thenReturn(1L); + ServiceOfferingVO serviceOfferingVO = Mockito.mock(ServiceOfferingVO.class); + serviceOfferingVO.setDiskOfferingStrictness(false); + when(serviceOfferingDao.findById(anyLong())).thenReturn(serviceOfferingVO); + + volumeApiServiceImpl.validateConditionsToReplaceDiskOfferingOfVolume(volumeVoMock, newDiskOfferingMock, storagePoolMock); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateConditionsToReplaceDiskOfferingOfVolumeTestRootVolumeWithDiskOfferingStrictnessTrue() { + Mockito.lenient().when(volumeVoMock.getVolumeType()).thenReturn(Type.ROOT); + Mockito.doReturn(vmInstanceMockId).when(volumeVoMock).getInstanceId(); + UserVmVO vm = Mockito.mock(UserVmVO.class); + when(_vmInstanceDao.findById(anyLong())).thenReturn(vm); + when(vm.getServiceOfferingId()).thenReturn(1L); + ServiceOfferingVO serviceOfferingVO = Mockito.mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findById(anyLong())).thenReturn(serviceOfferingVO); + when(serviceOfferingVO.getDiskOfferingStrictness()).thenReturn(true); volumeApiServiceImpl.validateConditionsToReplaceDiskOfferingOfVolume(volumeVoMock, newDiskOfferingMock, storagePoolMock); } diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index a9821094f06..c9c07c48b52 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -72,7 +72,6 @@ import com.cloud.offering.ServiceOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.GuestOSVO; -import com.cloud.storage.Storage; import com.cloud.storage.dao.GuestOSDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -96,9 +95,6 @@ public class UserVmManagerImplTest { @Mock private DiskOfferingDao diskOfferingDao; - @Mock - private ServiceOfferingVO serviceOfferingVO; - @Mock private DataCenterDao _dcDao; @Mock @@ -223,8 +219,9 @@ public class UserVmManagerImplTest { boolean ha = false; boolean useLocalStorage = false; - ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, Storage.ProvisioningType.THIN, useLocalStorage, false, null, false, null, + ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, false, null, false); + serviceOffering.setDiskOfferingId(1l); return serviceOffering; } @@ -471,16 +468,13 @@ public class UserVmManagerImplTest { VMTemplateVO template = Mockito.mock(VMTemplateVO.class); Mockito.when(template.getId()).thenReturn(1l); Mockito.when(template.getSize()).thenReturn(99L * GiB_TO_BYTES); - ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class); - Mockito.when(offering.getId()).thenReturn(1l); Mockito.when(templateDao.findById(Mockito.anyLong())).thenReturn(template); DiskOfferingVO diskfferingVo = Mockito.mock(DiskOfferingVO.class); - Mockito.when(diskOfferingDao.findById(Mockito.anyLong())).thenReturn(diskfferingVo); Mockito.when(diskfferingVo.getDiskSize()).thenReturn(offeringRootDiskSize); - long rootDiskSize = userVmManagerImpl.configureCustomRootDiskSize(customParameters, template, Hypervisor.HypervisorType.KVM, offering); + long rootDiskSize = userVmManagerImpl.configureCustomRootDiskSize(customParameters, template, Hypervisor.HypervisorType.KVM, diskfferingVo); Assert.assertEquals(expectedRootDiskSize, rootDiskSize); Mockito.verify(userVmManagerImpl, Mockito.times(timesVerifyIfHypervisorSupports)).verifyIfHypervisorSupportsRootdiskSizeOverride(Mockito.any()); diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerTest.java index 37cd6e51299..a3fb72c71bf 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerTest.java @@ -97,7 +97,6 @@ import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VMTemplateDao; @@ -452,7 +451,7 @@ public class UserVmManagerTest { boolean ha = false; boolean useLocalStorage = false; - ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, Storage.ProvisioningType.THIN, useLocalStorage, false, null, false, null, + ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, false, null, false); return serviceOffering; } @@ -517,7 +516,7 @@ public class UserVmManagerTest { AccountVO newAccount = new AccountVO("testaccount", 1, "networkdomain", (short)1, UUID.randomUUID().toString()); newAccount.setId(2L); - UserVmVO vm = new UserVmVO(10L, "test", "test", 1L, HypervisorType.Any, 1L, false, false, 1L, 1L, 1, 5L, "test", "test", 1L); + UserVmVO vm = new UserVmVO(10L, "test", "test", 1L, HypervisorType.Any, 1L, false, false, 1L, 1L, 1, 5L, "test", "test"); vm.setState(VirtualMachine.State.Stopped); when(_vmDao.findById(anyLong())).thenReturn(vm); diff --git a/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java b/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java index 49de99ad803..7dbfba304e8 100644 --- a/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java +++ b/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java @@ -48,7 +48,7 @@ public class UserVmDaoImplTest extends TestCase { // Persist the data. UserVmVO vo = new UserVmVO(vmId, instanceName, displayName, templateId, hypervisor, guestOsId, haEnabled, limitCpuUse, domainId, accountId, 1, serviceOfferingId, userdata, - name, diskOfferingId); + name); dao.persist(vo); vo = dao.findById(vmId); diff --git a/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java b/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java index 0d366483300..8d7a645e6bc 100644 --- a/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java +++ b/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java @@ -205,7 +205,7 @@ public class AffinityApiUnitTest { @Test(expected = InvalidParameterValueException.class) public void updateAffinityGroupVMRunning() throws ResourceInUseException { - UserVmVO vm = new UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, domainId, 200L, 1, 5L, "", "test", 1L); + UserVmVO vm = new UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, domainId, 200L, 1, 5L, "", "test"); vm.setState(VirtualMachine.State.Running); when(_vmDao.findById(10L)).thenReturn(vm); diff --git a/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java index 8aa4aa8277e..607bf0436d4 100644 --- a/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java @@ -270,7 +270,7 @@ public class AffinityGroupServiceImplTest { @Test(expected = InvalidParameterValueException.class) public void updateAffinityGroupVMRunning() throws ResourceInUseException { when(_acctMgr.finalizeOwner((Account)anyObject(), anyString(), anyLong(), anyLong())).thenReturn(acct); - UserVmVO vm = new UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, DOMAIN_ID, 200L, 1, 5L, "", "test", 1L); + UserVmVO vm = new UserVmVO(10L, "test", "test", 101L, HypervisorType.Any, 21L, false, false, DOMAIN_ID, 200L, 1, 5L, "", "test"); vm.setState(VirtualMachine.State.Running); when(_vmDao.findById(10L)).thenReturn(vm); diff --git a/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java b/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java index 0d4630a38d3..9fa67b0f2d2 100644 --- a/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java +++ b/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java @@ -21,7 +21,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; -import com.cloud.storage.Storage; import com.cloud.service.ServiceOfferingVO; import com.cloud.vm.VirtualMachine; @@ -32,8 +31,8 @@ public class ServiceOfferingVOTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - offeringCustom = new ServiceOfferingVO("custom", null, null, 500, 10, 10, false, "custom", Storage.ProvisioningType.THIN, false, false, "", false, VirtualMachine.Type.User, false); - offering = new ServiceOfferingVO("normal", 1, 1000, 500, 10, 10, false, "normal", Storage.ProvisioningType.THIN, false, false, "", false, VirtualMachine.Type.User, false); + offeringCustom = new ServiceOfferingVO("custom", null, null, 500, 10, 10, false, "custom", false, VirtualMachine.Type.User, false); + offering = new ServiceOfferingVO("normal", 1, 1000, 500, 10, 10, false, "normal", false, VirtualMachine.Type.User, false); } // Test restoreVm when VM state not in running/stopped case diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index a8c64f39d5f..eadb1b3c665 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -266,13 +266,13 @@ public class UnmanagedVMsManagerImplTest { when(templateDao.findByName(Mockito.anyString())).thenReturn(template); ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); when(serviceOffering.getId()).thenReturn(1L); - when(serviceOffering.getTags()).thenReturn(""); when(serviceOffering.isDynamic()).thenReturn(false); when(serviceOffering.getCpu()).thenReturn(instance.getCpuCores()); when(serviceOffering.getRamSize()).thenReturn(instance.getMemory()); when(serviceOffering.getSpeed()).thenReturn(instance.getCpuSpeed()); when(serviceOfferingDao.findById(Mockito.anyLong())).thenReturn(serviceOffering); DiskOfferingVO diskOfferingVO = Mockito.mock(DiskOfferingVO.class); + when(diskOfferingVO.getTags()).thenReturn(""); when(diskOfferingVO.isCustomized()).thenReturn(false); when(diskOfferingVO.getDiskSize()).thenReturn(Long.MAX_VALUE); when(diskOfferingDao.findById(Mockito.anyLong())).thenReturn(diskOfferingVO); diff --git a/test/integration/smoke/test_service_offerings.py b/test/integration/smoke/test_service_offerings.py index 3a942a10b62..1f171913b47 100644 --- a/test/integration/smoke/test_service_offerings.py +++ b/test/integration/smoke/test_service_offerings.py @@ -25,6 +25,7 @@ from marvin.lib.utils import (isAlmostEqual, cleanup_resources, random_gen) from marvin.lib.base import (ServiceOffering, + DiskOffering, Account, VirtualMachine) from marvin.lib.common import (list_service_offering, @@ -338,17 +339,17 @@ class TestServiceOfferings(cloudstackTestCase): cls.apiclient, cls.services["service_offerings"]["tiny"] ) - template = get_test_template( + cls.template = get_test_template( cls.apiclient, cls.zone.id, cls.hypervisor ) - if template == FAILED: + if cls.template == FAILED: assert False, "get_test_template() failed to return template" # Set Zones and disk offerings cls.services["small"]["zoneid"] = cls.zone.id - cls.services["small"]["template"] = template.id + cls.services["small"]["template"] = cls.template.id # Create VMs, NAT Rules etc @@ -614,6 +615,204 @@ class TestServiceOfferings(cloudstackTestCase): ) return + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_05_disk_offering_strictness_true(self): + """Test to see change service offering is not possible when disk offering strictness is set to true + """ + # Validate the following + # 1. Create service offering linked a disk offering and disk offering strictness is true + # 2. Create a VM with that service offering + # 3. Create another service offering with a different disk offering + # 4. Try change service offering for VM and it will fail since disk offering strictness is true (not allowed to change the disk offering) + + if self.hypervisor.lower() == "lxc": + self.skipTest("Skipping this test for {} due to bug CS-38153".format(self.hypervisor)) + offering_data = { + 'displaytext': 'TestDiskOfferingStrictnessTrue', + 'cpuspeed': 512, + 'cpunumber': 2, + 'name': 'TestDiskOfferingStrictnessTrue', + 'memory': 1024, + 'diskofferingstrictness': True + } + + self.serviceOfferingWithDiskOfferingStrictnessTrue = ServiceOffering.create( + self.apiclient, + offering_data, + ) + self._cleanup.append(self.serviceOfferingWithDiskOfferingStrictnessTrue) + + self.virtual_machine_with_diskoffering_strictness_true = VirtualMachine.create( + self.apiclient, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.serviceOfferingWithDiskOfferingStrictnessTrue.id, + mode=self.services["mode"] + ) + + try: + self.virtual_machine_with_diskoffering_strictness_true.stop(self.apiclient) + + timeout = self.services["timeout"] + + while True: + time.sleep(self.services["sleep"]) + + # Ensure that VM is in stopped state + list_vm_response = list_virtual_machines( + self.apiclient, + id=self.virtual_machine_with_diskoffering_strictness_true.id + ) + + if isinstance(list_vm_response, list): + vm = list_vm_response[0] + if vm.state == 'Stopped': + self.debug("VM state: %s" % vm.state) + break + + if timeout == 0: + raise Exception( + "Failed to stop VM (ID: %s) in change service offering" % vm.id) + + timeout = timeout - 1 + except Exception as e: + self.fail("Failed to stop VM: %s" % e) + + offering_data = { + 'displaytext': 'TestDiskOfferingStrictnessTrue2', + 'cpuspeed': 1000, + 'cpunumber': 2, + 'name': 'TestDiskOfferingStrictnessTrue2', + 'memory': 1024, + 'diskofferingstrictness': True + } + + self.serviceOfferingWithDiskOfferingStrictnessTrue2 = ServiceOffering.create( + self.apiclient, + offering_data, + ) + self._cleanup.append(self.serviceOfferingWithDiskOfferingStrictnessTrue2) + cmd = changeServiceForVirtualMachine.changeServiceForVirtualMachineCmd() + cmd.id = self.virtual_machine_with_diskoffering_strictness_true.id + cmd.serviceofferingid = self.serviceOfferingWithDiskOfferingStrictnessTrue2.id + + with self.assertRaises(Exception) as e: + self.apiclient.changeServiceForVirtualMachine(cmd) + self.debug("Upgrade VM with new service offering having different disk offering operation failed as expected with exception: %s" % + e.exception) + return + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_06_disk_offering_strictness_false(self): + """Test to see change service offering is possible when disk offering strictness is set to false + """ + # Validate the following + # 1. Create service offering linked a disk offering and disk offering strictness is false + # 2. Create a VM with that service offering + # 3. Create another service offering with a different disk offering and disk offering strictness is false + # 4. Try change service offering for VM should succeed + + if self.hypervisor.lower() == "lxc": + self.skipTest("Skipping this test for {} due to bug CS-38153".format(self.hypervisor)) + + offering_data = { + 'displaytext': 'TestDiskOfferingStrictnessFalse', + 'cpuspeed': 512, + 'cpunumber': 2, + 'name': 'TestDiskOfferingStrictnessFalse', + 'memory': 1024, + 'diskofferingstrictness': False + } + + self.serviceOfferingWithDiskOfferingStrictnessFalse = ServiceOffering.create( + self.apiclient, + offering_data, + ) + self._cleanup.append(self.serviceOfferingWithDiskOfferingStrictnessFalse) + + self.virtual_machine_with_diskoffering_strictness_false = VirtualMachine.create( + self.apiclient, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.serviceOfferingWithDiskOfferingStrictnessFalse.id, + mode=self.services["mode"] + ) + + try: + self.virtual_machine_with_diskoffering_strictness_false.stop(self.apiclient) + + timeout = self.services["timeout"] + + while True: + time.sleep(self.services["sleep"]) + + # Ensure that VM is in stopped state + list_vm_response = list_virtual_machines( + self.apiclient, + id=self.virtual_machine_with_diskoffering_strictness_false.id + ) + + if isinstance(list_vm_response, list): + vm = list_vm_response[0] + if vm.state == 'Stopped': + self.debug("VM state: %s" % vm.state) + break + + if timeout == 0: + raise Exception( + "Failed to stop VM (ID: %s) in change service offering" % vm.id) + + timeout = timeout - 1 + except Exception as e: + self.fail("Failed to stop VM: %s" % e) + + self.disk_offering2 = DiskOffering.create( + self.apiclient, + self.services["disk_offering"], + ) + self._cleanup.append(self.disk_offering2) + offering_data = { + 'displaytext': 'TestDiskOfferingStrictnessFalse2', + 'cpuspeed': 1000, + 'cpunumber': 2, + 'name': 'TestDiskOfferingStrictnessFalse2', + 'memory': 1024, + 'diskofferingstrictness': False, + 'diskofferingid': self.disk_offering2.id + } + + self.serviceOfferingWithDiskOfferingStrictnessFalse2 = ServiceOffering.create( + self.apiclient, + offering_data, + ) + self._cleanup.append(self.serviceOfferingWithDiskOfferingStrictnessFalse2) + cmd = changeServiceForVirtualMachine.changeServiceForVirtualMachineCmd() + cmd.id = self.virtual_machine_with_diskoffering_strictness_false.id + cmd.serviceofferingid = self.serviceOfferingWithDiskOfferingStrictnessFalse2.id + self.apiclient.changeServiceForVirtualMachine(cmd) + + list_vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine_with_diskoffering_strictness_false.id + ) + + vm_response = list_vm_response[0] + self.assertEqual( + vm_response.id, + self.virtual_machine_with_diskoffering_strictness_false.id, + "Check virtual machine ID of upgraded VM" + ) + + self.assertEqual( + vm_response.serviceofferingid, + self.serviceOfferingWithDiskOfferingStrictnessFalse2.id, + "Check service offering of the VM" + ) + + return + class TestCpuCapServiceOfferings(cloudstackTestCase): def setUp(self): diff --git a/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py index 13082859682..c9b93494dc9 100644 --- a/test/integration/smoke/test_volumes.py +++ b/test/integration/smoke/test_volumes.py @@ -606,38 +606,6 @@ class TestVolumes(cloudstackTestCase): with self.assertRaises(Exception): self.apiClient.resizeVolume(cmd) - # Ok, now let's try and resize a volume that is not custom. - cmd.id = self.volume.id - cmd.diskofferingid = self.services['diskofferingid'] - cmd.size = 4 - - self.debug( - "Attaching volume (ID: %s) to VM (ID: %s)" % ( - self.volume.id, - self.virtual_machine.id) - ) - # attach the volume - self.virtual_machine.attach_volume(self.apiClient, self.volume) - self.attached = True - # stop the vm if it is on xenserver - hosts = Host.list(self.apiClient, id=self.virtual_machine.hostid) - self.assertTrue(isinstance(hosts, list)) - self.assertTrue(len(hosts) > 0) - self.debug("Found %s host" % hosts[0].hypervisor) - - if hosts[0].hypervisor == "XenServer": - self.virtual_machine.stop(self.apiClient) - elif hosts[0].hypervisor.lower() == "hyperv": - self.skipTest("Resize Volume is unsupported on Hyper-V") - - # Attempting to resize it should throw an exception, as we're using a non - # customisable disk offering, therefore our size parameter should be ignored - with self.assertRaises(Exception): - self.apiClient.resizeVolume(cmd) - - if hosts[0].hypervisor == "XenServer": - self.virtual_machine.start(self.apiClient) - time.sleep(30) return @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="true") @@ -758,6 +726,67 @@ class TestVolumes(cloudstackTestCase): time.sleep(30) return + @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="true") + def test_12_resize_volume_with_only_size_parameter(self): + """Test resize a volume by providing only size parameter, disk offering id is not mandatory""" + # Verify the size is the new size is what we wanted it to be. + self.debug( + "Attaching volume (ID: %s) to VM (ID: %s)" % ( + self.volume.id, + self.virtual_machine.id + )) + + self.virtual_machine.attach_volume(self.apiClient, self.volume) + self.attached = True + hosts = Host.list(self.apiClient, id=self.virtual_machine.hostid) + self.assertTrue(isinstance(hosts, list)) + self.assertTrue(len(hosts) > 0) + self.debug("Found %s host" % hosts[0].hypervisor) + + if hosts[0].hypervisor == "XenServer": + self.virtual_machine.stop(self.apiClient) + elif hosts[0].hypervisor.lower() == "hyperv": + self.skipTest("Resize Volume is unsupported on Hyper-V") + + # resize the data disk + self.debug("Resize Volume ID: %s" % self.volume.id) + + cmd = resizeVolume.resizeVolumeCmd() + cmd.id = self.volume.id + cmd.size = 20 + + self.apiClient.resizeVolume(cmd) + + count = 0 + success = False + while count < 3: + list_volume_response = Volume.list( + self.apiClient, + id=self.volume.id, + type='DATADISK' + ) + for vol in list_volume_response: + if vol.id == self.volume.id and int(vol.size) == (20 * (1024 ** 3)) and vol.state == 'Ready': + success = True + if success: + break + else: + time.sleep(10) + count += 1 + + self.assertEqual( + success, + True, + "Check if the data volume resized appropriately" + ) + + # start the vm if it is on xenserver + + if hosts[0].hypervisor == "XenServer": + self.virtual_machine.start(self.apiClient) + time.sleep(30) + return + @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false") def test_09_delete_detached_volume(self): """Delete a Volume unattached to an VM diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 910e8bd7c04..6fa55153c8e 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -2305,6 +2305,12 @@ class ServiceOffering: if "dynamicscalingenabled" in services: cmd.dynamicscalingenabled = services["dynamicscalingenabled"] + if "diskofferingstrictness" in services: + cmd.diskofferingstrictness = services["diskofferingstrictness"] + + if "diskofferingid" in services: + cmd.diskofferingid = services["diskofferingid"] + # Service Offering private to that domain if domainid: cmd.domainid = domainid diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 6f25164ba77..bd4bdf80f9e 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -470,6 +470,7 @@ "label.asyncbackup": "Async Backup", "label.authentication.method": "Authentication Method", "label.authentication.sshkey": "System SSH Key", +"label.automigrate.volume": "Auto migrate volume to another storage pool if required", "label.author.email": "Author e-mail", "label.author.name": "Author name", "label.auto.assign": "Automatically assign", @@ -558,6 +559,7 @@ "label.change.ip.addess": "Change IP Address", "label.change.ipaddress": "Change IP address for NIC", "label.change.service.offering": "Change service offering", +"label.change.offering.for.volume": "Change disk offering for the volume", "label.change.value": "Change value", "label.character": "Character", "label.chassis": "Chassis", @@ -863,6 +865,9 @@ "label.duration.in.sec": "Duration (in sec)", "label.dynamicscalingenabled": "Dynamic Scaling Enabled", "label.dynamicscalingenabled.tooltip": "VM can dynamically scale only when dynamic scaling is enabled on template, service offering and global setting", +"label.diskofferingstrictness": "Disk Offering Strictness", +"label.disksizestrictness": "Disk Size Strictness", +"label.computeonly.offering": "Compute only Disk Offering", "label.edit": "Edit", "label.edit.acl.list": "Edit ACL List", "label.edit.acl.rule": "Edit ACL rule", @@ -1440,6 +1445,7 @@ "label.migrate.to.host": "Migrate to host", "label.migrate.to.storage": "Migrate to storage", "label.migrate.volume": "Migrate Volume", +"message.migrate.volume.tooltip": "Volume can be migrated to any suitable storage pool. Admin has to choose the appropriate disk offering to replace, that supports the new storage pool", "label.migrate.volume.newdiskoffering.desc": "This option allows administrators to replace the old disk offering, using one that better suits the new placement of the volume.", "label.migrate.volume.to.primary.storage": "Migrate volume to another primary storage", "label.migrate.with.storage": "Migrate with storage", @@ -1607,6 +1613,8 @@ "label.override.rootdisk.size": "Override Root Disk Size", "label.overrideguesttraffic": "Override Guest-Traffic", "label.overridepublictraffic": "Override Public-Traffic", +"label.override.diskoffering": "Override Disk Offering", +"label.override.root.diskoffering": "Override Root Disk Offering", "label.ovf.properties": "vApp Properties", "label.ovm3cluster": "Native Clustering", "label.ovm3networklabel": "OVM3 Traffic Label", @@ -2675,6 +2683,10 @@ "message.basic.mode.desc": "Choose this network model if you do *not* want to enable any VLAN support. All virtual instances created under this network model will be assigned an IP directly from the network and security groups are used to provide security and segregation.", "message.certificate.upload.processing": "Certificate upload in progress", "message.change.offering.confirm": "Please confirm that you wish to change the service offering of this virtual instance.", +"message.confirm.change.offering.for.volume": "Please confirm that you want to change disk offering for the volume", +"message.change.offering.for.volume.failed": "Change offering for the volume failed", +"message.change.offering.for.volume.processing": "Changing offering for the volume...", +"message.change.offering.for.volume": "Successfully changed offering for the volume", "message.change.password": "Please change your password", "message.cluster.dedicated": "Cluster Dedicated", "message.cluster.dedication.released": "Cluster dedication released", diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index 05da70a98b1..3480aa8016f 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -31,7 +31,7 @@ export default { params: { isrecursive: 'true' }, columns: ['name', 'displaytext', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'], details: () => { - var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled'] + var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness'] if (store.getters.apis.createServiceOffering && store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') @@ -101,7 +101,7 @@ export default { permission: ['listServiceOfferings', 'listInfrastructure'], params: { issystem: 'true', isrecursive: 'true' }, columns: ['name', 'systemvmtype', 'cpunumber', 'cpuspeed', 'memory', 'storagetype', 'order'], - details: ['name', 'id', 'displaytext', 'systemvmtype', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created', 'dynamicscalingenabled'], + details: ['name', 'id', 'displaytext', 'systemvmtype', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness'], actions: [{ api: 'createServiceOffering', icon: 'plus', @@ -141,7 +141,7 @@ export default { params: { isrecursive: 'true' }, columns: ['name', 'displaytext', 'disksize', 'domain', 'zone', 'order'], details: () => { - var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'tags', 'domain', 'zone', 'created'] + var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'disksizestrictness', 'iscustomizediops', 'tags', 'domain', 'zone', 'created'] if (store.getters.apis.createDiskOffering && store.getters.apis.createDiskOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index 48f10d08547..9100104e7f4 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -197,6 +197,17 @@ export default { popup: true, component: () => import('@/views/storage/MigrateVolume.vue') }, + { + api: 'changeOfferingForVolume', + icon: 'swap', + docHelp: 'adminguide/storage.html#id2', + label: 'label.change.offering.for.volume', + args: ['id', 'diskofferingid', 'size', 'miniops', 'maxiops', 'automigrate'], + dataView: true, + show: (record, store) => { return ['Allocated', 'Ready'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) }, + popup: true, + component: () => import('@/views/storage/ChangeOfferingForVolume.vue') + }, { api: 'extractVolume', icon: 'cloud-download', diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index f561952f77f..f180cf37636 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -148,8 +148,9 @@ {{ $t('label.override.rootdisk.size') }}
{{ this.$t('message.deployasis') }}
@@ -273,6 +274,65 @@
+ + {{ $t('label.override.root.diskoffering') }} + + + + + + + + + + @@ -819,6 +879,7 @@ export default { networksAdd: [], zone: {}, sshKeyPair: {}, + overrideDiskOffering: {}, templateFilter: [ 'featured', 'community', @@ -839,10 +900,12 @@ export default { dataPreFill: {}, showDetails: false, showRootDiskSizeChanger: false, + showOverrideDiskOfferingOption: false, securitygroupids: [], rootDiskSizeFixed: 0, error: false, diskSelected: {}, + rootDiskSelected: {}, diskIOpsMin: 0, diskIOpsMax: 0, minIops: 0, @@ -1092,7 +1155,7 @@ export default { return this.diskSelected?.iscustomizediops || false }, isCustomizedIOPS () { - return this.serviceOffering?.iscustomizediops || false + return this.rootDiskSelected?.iscustomizediops || this.serviceOffering?.iscustomizediops || false } }, watch: { @@ -1126,7 +1189,25 @@ export default { } this.serviceOffering = _.find(this.options.serviceOfferings, (option) => option.id === instanceConfig.computeofferingid) - this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === instanceConfig.diskofferingid) + if (this.serviceOffering?.diskofferingid) { + if (iso) { + this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === this.serviceOffering.diskofferingid) + } else { + instanceConfig.overridediskofferingid = this.serviceOffering.diskofferingid + } + } + if (!iso && this.diskSelected) { + this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === instanceConfig.diskofferingid) + } + if (this.rootDiskSelected?.id) { + instanceConfig.overridediskofferingid = this.rootDiskSelected.id + } + console.log('overrided value ' + instanceConfig.overridediskofferingid) + if (instanceConfig.overridediskofferingid) { + this.overrideDiskOffering = _.find(this.options.diskOfferings, (option) => option.id === instanceConfig.overridediskofferingid) + } else { + this.overrideDiskOffering = null + } this.zone = _.find(this.options.zones, (option) => option.id === instanceConfig.zoneid) this.affinityGroups = _.filter(this.options.affinityGroups, (option) => _.includes(instanceConfig.affinitygroupids, option.id)) this.networks = _.filter(this.options.networks, (option) => _.includes(instanceConfig.networkids, option.id)) @@ -1244,6 +1325,7 @@ export default { }) this.form.getFieldDecorator('computeofferingid', { initialValue: undefined, preserve: true }) this.form.getFieldDecorator('diskofferingid', { initialValue: undefined, preserve: true }) + this.form.getFieldDecorator('overridediskofferingid', { initialValue: undefined, preserve: true }) this.form.getFieldDecorator('multidiskoffering', { initialValue: undefined, preserve: true }) this.form.getFieldDecorator('affinitygroupids', { initialValue: [], preserve: true }) this.form.getFieldDecorator('networkids', { initialValue: [], preserve: true }) @@ -1369,6 +1451,14 @@ export default { getImg (image) { return 'data:image/png;charset=utf-8;base64, ' + image }, + updateOverrideRootDiskShowParam (val) { + if (val) { + this.showRootDiskSizeChanger = false + } else { + this.rootDiskSelected = null + } + this.showOverrideDiskOfferingOption = val + }, async fetchDataByZone (zoneId) { this.fillValue('zoneid') this.options.zones = await this.fetchZones(zoneId) @@ -1503,6 +1593,17 @@ export default { diskofferingid: id }) }, + updateOverrideDiskOffering (id) { + if (id === '0') { + this.form.setFieldsValue({ + overridediskofferingid: undefined + }) + return + } + this.form.setFieldsValue({ + overridediskofferingid: id + }) + }, updateMultiDiskOffering (value) { this.form.setFieldsValue({ multidiskoffering: value @@ -1634,7 +1735,8 @@ export default { } else { deployVmData.templateid = values.isoid } - if (this.showRootDiskSizeChanger && values.rootdisksize && values.rootdisksize > 0) { + + if (values.rootdisksize && values.rootdisksize > 0) { deployVmData.rootdisksize = values.rootdisksize } else if (this.rootDiskSizeFixed > 0) { deployVmData.rootdisksize = this.rootDiskSizeFixed @@ -1661,6 +1763,9 @@ export default { if (this.selectedTemplateConfiguration) { deployVmData['details[0].configurationId'] = this.selectedTemplateConfiguration.id } + if (!this.serviceOffering.diskofferingstrictness && values.overridediskofferingid) { + deployVmData.overridediskofferingid = values.overridediskofferingid + } if (this.isCustomizedIOPS) { deployVmData['details[0].minIops'] = this.minIops deployVmData['details[0].maxIops'] = this.maxIops @@ -2222,6 +2327,9 @@ export default { onSelectDiskSize (rowSelected) { this.diskSelected = rowSelected }, + onSelectRootDiskSize (rowSelected) { + this.rootDiskSelected = rowSelected + }, updateIOPSValue (input, value) { this[input] = value }, diff --git a/ui/src/views/compute/ScaleVM.vue b/ui/src/views/compute/ScaleVM.vue index a0cf5b197a1..4a3200a3631 100644 --- a/ui/src/views/compute/ScaleVM.vue +++ b/ui/src/views/compute/ScaleVM.vue @@ -48,6 +48,14 @@ @update-compute-cpuspeed="updateFieldValue" @update-compute-memory="updateFieldValue" /> + + + + +
{{ this.$t('label.cancel') }} {{ this.$t('label.ok') }} @@ -78,6 +86,7 @@ export default { offeringsMap: {}, offerings: [], selectedOffering: {}, + autoMigrate: true, total: 0, params: { id: this.resource.id }, loading: false, @@ -86,6 +95,10 @@ export default { memoryKey: 'details[0].memory' } }, + beforeCreate () { + this.form = this.$form.createForm(this) + this.apiParams = this.$getApiParams('scaleVirtualMachine') + }, created () { this.fetchData({ keyword: '', @@ -145,6 +158,7 @@ export default { this.params.serviceofferingid = id this.selectedOffering = this.offeringsMap[id] + this.params.automigrate = this.autoMigrate }, updateFieldValue (name, value) { this.params[name] = value diff --git a/ui/src/views/compute/wizard/ComputeSelection.vue b/ui/src/views/compute/wizard/ComputeSelection.vue index 5fe75d68178..8d68fb9c9d3 100644 --- a/ui/src/views/compute/wizard/ComputeSelection.vue +++ b/ui/src/views/compute/wizard/ComputeSelection.vue @@ -16,7 +16,7 @@ // under the License.