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 745e6a0f3c6e..32194227448e 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 @@ -127,6 +127,9 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.SERVICE_OFFERING_DETAILS, type = CommandType.MAP, description = "details for planner, used to store specific parameters") private Map details; + @Parameter(name = ApiConstants.ROOT_DISK_SIZE, type = CommandType.LONG, since = "4.15", description = "the Root disk size in GB.") + private Long rootDiskSize; + @Parameter(name = ApiConstants.BYTES_READ_RATE, type = CommandType.LONG, required = false, description = "bytes read rate of the disk offering") private Long bytesReadRate; @@ -324,6 +327,10 @@ public Map getDetails() { return detailsMap; } + public Long getRootDiskSize() { + return rootDiskSize; + } + public Long getBytesReadRate() { return bytesReadRate; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java index 2471c8091b3d..1f1bc6a8fc11 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java @@ -87,6 +87,13 @@ public ResizeVolumeCmd(Long id, Long minIops, Long maxIops) { this.maxIops = maxIops; } + public ResizeVolumeCmd(Long id, Long minIops, Long maxIops, long diskOfferingId) { + this.id = id; + this.minIops = minIops; + this.maxIops = maxIops; + this.newDiskOfferingId = diskOfferingId; + } + //TODO use the method getId() instead of this one. public Long getEntityId() { return id; 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 e13735bd1c33..05fcfbdc7b2e 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 @@ -200,6 +200,10 @@ public class ServiceOfferingResponse extends BaseResponse { @Param(description = "the vsphere storage policy tagged to the service offering in case of VMware", since = "4.15") private String vsphereStoragePolicy; + @SerializedName(ApiConstants.ROOT_DISK_SIZE) + @Param(description = "Root disk size in GB", since = "4.15") + private Long rootDiskSize; + public ServiceOfferingResponse() { } @@ -468,4 +472,7 @@ public void setVsphereStoragePolicy(String vsphereStoragePolicy) { this.vsphereStoragePolicy = vsphereStoragePolicy; } + public void setRootDiskSize(Long rootDiskSize) { + this.rootDiskSize = rootDiskSize; + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql index bd4d1ca146a0..706ea0a346e6 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41400to41500.sql @@ -402,6 +402,7 @@ CREATE VIEW `cloud`.`service_offering_view` AS `disk_offering`.`iops_write_rate_max` AS `iops_write_rate_max`, `disk_offering`.`iops_write_rate_max_length` AS `iops_write_rate_max_length`, `disk_offering`.`cache_mode` AS `cache_mode`, + `disk_offering`.`disk_size` AS `root_disk_size`, `service_offering`.`cpu` AS `cpu`, `service_offering`.`speed` AS `speed`, `service_offering`.`ram_size` AS `ram_size`, @@ -816,7 +817,6 @@ INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_vers INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '6.7.2', 'solaris11_64Guest', 328, now(), 0); INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '6.7.3', 'solaris11_64Guest', 328, now(), 0); - -- Add VMware Photon (64 bit) as support guest os INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (329, UUID(), 7, 'VMware Photon (64 bit)', now()); -- VMware Photon (64 bit) VMWare guest os mapping diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java index 4c025b99e9dc..87b03748dbcd 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java @@ -113,6 +113,7 @@ public ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offeringResponse.setObjectName("serviceoffering"); offeringResponse.setIscutomized(offering.isDynamic()); offeringResponse.setCacheMode(offering.getCacheMode()); + if (offeringDetails != null && !offeringDetails.isEmpty()) { String vsphereStoragePolicyId = offeringDetails.get(ApiConstants.STORAGE_POLICY); if (vsphereStoragePolicyId != null) { @@ -121,6 +122,9 @@ public ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offeringResponse.setVsphereStoragePolicy(vsphereStoragePolicyVO.getName()); } } + + offeringResponse.setRootDiskSize(offering.getRootDiskSize()); + return offeringResponse; } 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 e025da5de3f0..4d9b2cd5c0b6 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 @@ -190,6 +190,9 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit @Column(name = "vsphere_storage_policy") String vsphereStoragePolicy; + @Column(name = "root_disk_size") + private Long rootDiskSize; + public ServiceOfferingJoinVO() { } @@ -363,7 +366,6 @@ public Long getIopsWriteRate() { public Long getIopsWriteRateMaxLength() { return iopsWriteRateMaxLength; } - public boolean isDynamic() { return cpu == null || speed == null || ramSize == null; } @@ -392,4 +394,7 @@ public String getVsphereStoragePolicy() { return vsphereStoragePolicy; } + public Long getRootDiskSize() { + return rootDiskSize ; + } } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 9d1cffcc0e18..c664182f04f5 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -430,6 +430,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati private static final Set VPC_ONLY_PROVIDERS = Sets.newHashSet(Provider.VPCVirtualRouter, Provider.JuniperContrailVpcRouter, Provider.InternalLbVm); + private static final long GiB_TO_BYTES = 1024 * 1024 * 1024; + @Override public boolean configure(final String name, final Map params) throws ConfigurationException { final String maxVolumeSizeInGbString = _configDao.getValue(Config.MaxVolumeSize.key()); @@ -2471,7 +2473,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) 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, isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(), + cmd.getNetworkRate(), cmd.getDeploymentPlanner(), details, cmd.getRootDiskSize(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(), cmd.getBytesReadRate(), cmd.getBytesReadRateMax(), cmd.getBytesReadRateMaxLength(), cmd.getBytesWriteRate(), cmd.getBytesWriteRateMax(), cmd.getBytesWriteRateMaxLength(), cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(), @@ -2482,7 +2484,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) protected ServiceOfferingVO createServiceOffering(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 String provisioningType, 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, final Boolean isCustomizedIops, Long minIops, Long maxIops, + 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, @@ -2543,6 +2545,12 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole } } + 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); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index ad3dd30ab8d1..be2d52e3c441 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -894,7 +894,7 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); DiskOfferingVO newDiskOffering = null; - if (cmd.getNewDiskOfferingId() != null && volume.getDiskOfferingId() != cmd.getNewDiskOfferingId()) { + if (cmd.getNewDiskOfferingId() != null) { newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId()); } @@ -913,6 +913,12 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep // if we are to use the existing disk offering if (newDiskOffering == null) { + if (volume.getVolumeType().equals(Volume.Type.ROOT) && diskOffering.getDiskSize() > 0) { + throw new InvalidParameterValueException( + "Failed to resize Root volume. The service offering of this Volume has been configured with a root disk size; " + + "on such case a Root Volume can only be resized when changing to another Service Offering with a Root disk size. " + + "For more details please check out the Official Resizing Volumes documentation."); + } newSize = cmd.getSize(); newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve(); @@ -958,10 +964,6 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep throw new InvalidParameterValueException("Requested disk offering has been removed."); } - if (!DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) { - throw new InvalidParameterValueException("Requested disk offering type is invalid."); - } - if (diskOffering.getTags() != null) { if (!StringUtils.areTagsEqual(diskOffering.getTags(), newDiskOffering.getTags())) { throw new InvalidParameterValueException("The tags on the new and old disk offerings must match."); @@ -972,7 +974,9 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep _configMgr.checkDiskOfferingAccess(_accountMgr.getActiveAccountById(volume.getAccountId()), newDiskOffering, _dcDao.findById(volume.getDataCenterId())); - if (newDiskOffering.isCustomized()) { + if (newDiskOffering.getDiskSize() > 0 && DiskOfferingVO.Type.Service.equals(newDiskOffering.getType())) { + newSize = newDiskOffering.getDiskSize(); + } else if (newDiskOffering.isCustomized()) { newSize = cmd.getSize(); if (newSize == null) { @@ -989,9 +993,9 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep newSize = newDiskOffering.getDiskSize(); } - if (!volume.getSize().equals(newSize) && !volume.getVolumeType().equals(Volume.Type.DATADISK)) { - throw new InvalidParameterValueException("Only data volumes can be resized via a new disk offering."); - } + Long instanceId = volume.getInstanceId(); + VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId); + checkIfVolumeIsRootAndVmIsRunning(newSize, volume, vmInstanceVO); if (newDiskOffering.isCustomizedIops() != null && newDiskOffering.isCustomizedIops()) { newMinIops = cmd.getMinIops() != null ? cmd.getMinIops() : volume.getMinIops(); @@ -1140,6 +1144,13 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep shrinkOk); } + private void checkIfVolumeIsRootAndVmIsRunning(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) { + if (!volume.getSize().equals(newSize) && volume.getVolumeType().equals(Volume.Type.ROOT) && !State.Stopped.equals(vmInstanceVO.getState())) { + throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] when VM is not on Stopped State. VM %s is in state %.", volume.getName(), vmInstanceVO + .getInstanceName(), vmInstanceVO.getState())); + } + } + private void validateIops(Long minIops, Long maxIops) { if ((minIops == null && maxIops != null) || (minIops != null && maxIops == null)) { throw new InvalidParameterValueException("Either 'miniops' and 'maxiops' must both be provided or neither must be provided."); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index a931159ea211..1aa452cf4165 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -1183,24 +1183,27 @@ private UserVm upgradeStoppedVirtualMachine(Long vmId, Long svcOffId, Map vols = _volsDao.findReadyRootVolumesByInstance(vmInstance.getId()); for (final VolumeVO rootVolumeOfVm : vols) { - rootVolumeOfVm.setDiskOfferingId(newROOTDiskOffering.getId()); + DiskOfferingVO currentRootDiskOffering = _diskOfferingDao.findById(rootVolumeOfVm.getDiskOfferingId()); - _volsDao.update(rootVolumeOfVm.getId(), rootVolumeOfVm); + ResizeVolumeCmd resizeVolumeCmd = prepareResizeVolumeCmd(rootVolumeOfVm, currentRootDiskOffering, newRootDiskOffering); - ResizeVolumeCmd resizeVolumeCmd = new ResizeVolumeCmd(rootVolumeOfVm.getId(), newROOTDiskOffering.getMinIops(), newROOTDiskOffering.getMaxIops()); + if (rootVolumeOfVm.getDiskOfferingId() != newRootDiskOffering.getId()) { + rootVolumeOfVm.setDiskOfferingId(newRootDiskOffering.getId()); + _volsDao.update(rootVolumeOfVm.getId(), rootVolumeOfVm); + } _volumeService.resizeVolume(resizeVolumeCmd); } - // 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())); - _itMgr.upgradeVmDb(vmId, newServiceOffering, currentServiceOffering); // Increment or decrement CPU and Memory count accordingly. @@ -1221,6 +1224,40 @@ private UserVm upgradeStoppedVirtualMachine(Long vmId, Long svcOffId, Map + * If the Service Offering was configured with a root disk size (size > 0) then it can only resize to an offering with a larger disk + * or to an offering with a root size of zero, which is the default behavior. + */ + protected ResizeVolumeCmd prepareResizeVolumeCmd(VolumeVO rootVolume, DiskOfferingVO currentRootDiskOffering, DiskOfferingVO newRootDiskOffering) { + if (rootVolume == null) { + throw new InvalidParameterValueException("Could not find Root volume for the VM while preparing the Resize Volume Command."); + } + if (currentRootDiskOffering == null) { + throw new InvalidParameterValueException("Could not find Disk Offering matching the provided current Root Offering ID."); + } + if (newRootDiskOffering == null) { + throw new InvalidParameterValueException("Could not find Disk Offering matching the provided Offering ID for resizing Root volume."); + } + + ResizeVolumeCmd resizeVolumeCmd = new ResizeVolumeCmd(rootVolume.getId(), newRootDiskOffering.getMinIops(), newRootDiskOffering.getMaxIops()); + + long newNewOfferingRootSizeInBytes = newRootDiskOffering.getDiskSize(); + long newNewOfferingRootSizeInGiB = newNewOfferingRootSizeInBytes / GiB_TO_BYTES; + long currentRootDiskOfferingGiB = currentRootDiskOffering.getDiskSize() / GiB_TO_BYTES; + if (newNewOfferingRootSizeInBytes > currentRootDiskOffering.getDiskSize()) { + resizeVolumeCmd = new ResizeVolumeCmd(rootVolume.getId(), newRootDiskOffering.getMinIops(), newRootDiskOffering.getMaxIops(), newRootDiskOffering.getId()); + s_logger.debug(String.format("Preparing command to resize VM Root disk from %d GB to %d GB; current offering: %s, new offering: %s.", currentRootDiskOfferingGiB, + newNewOfferingRootSizeInGiB, currentRootDiskOffering.getName(), newRootDiskOffering.getName())); + } else if (newNewOfferingRootSizeInBytes > 0l && newNewOfferingRootSizeInBytes < currentRootDiskOffering.getDiskSize()) { + throw new InvalidParameterValueException(String.format( + "Failed to resize Root volume. The new Service Offering [id: %d, name: %s] has a smaller disk size [%d GB] than the current disk [%d GB].", + newRootDiskOffering.getId(), newRootDiskOffering.getName(), newNewOfferingRootSizeInGiB, currentRootDiskOfferingGiB)); + } + return resizeVolumeCmd; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_NIC_CREATE, eventDescription = "Creating Nic", async = true) public UserVm addNicToVirtualMachine(AddNicToVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException { @@ -3529,26 +3566,9 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe } // check if account/domain is with in resource limits to create a new vm boolean isIso = Storage.ImageFormat.ISO == template.getFormat(); - long size = 0; - // custom root disk size, resizes base template to larger size - if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { - // only KVM, XenServer and VMware supports rootdisksize override - if (!(hypervisorType == HypervisorType.KVM || hypervisorType == HypervisorType.XenServer || hypervisorType == HypervisorType.VMware || hypervisorType == HypervisorType.Simulator)) { - throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override"); - } - Long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1); - if (rootDiskSize <= 0) { - throw new InvalidParameterValueException("Root disk size should be a positive number."); - } - size = rootDiskSize * GiB_TO_BYTES; - } else { - // For baremetal, size can be null - Long templateSize = _templateDao.findById(template.getId()).getSize(); - if (templateSize != null) { - size = templateSize; - } - } + long size = configureCustomRootDiskSize(customParameters, template, hypervisorType, offering); + if (diskOfferingId != null) { DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); if (diskOffering != null && diskOffering.isCustomized()) { @@ -3865,6 +3885,46 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe return vm; } + /** + * 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) { + verifyIfHypervisorSupportsRootdiskSizeOverride(hypervisorType); + DiskOfferingVO diskOffering = _diskOfferingDao.findById(serviceOffering.getId()); + long rootDiskSizeInBytes = diskOffering.getDiskSize(); + 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)); + return rootDiskSizeInBytes; + } + + if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { + Long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1); + if (rootDiskSize <= 0) { + throw new InvalidParameterValueException("Root disk size should be a positive number."); + } + return rootDiskSize * GiB_TO_BYTES; + } else { + // For baremetal, size can be 0 (zero) + Long templateSize = _templateDao.findById(template.getId()).getSize(); + if (templateSize != null) { + return templateSize; + } + } + return 0; + } + + /** + * Only KVM, XenServer and VMware supports rootdisksize override + * @throws InvalidParameterValueException if the hypervisor does not support rootdisksize override + */ + protected void verifyIfHypervisorSupportsRootdiskSizeOverride(HypervisorType hypervisorType) { + if (!(hypervisorType == HypervisorType.KVM || hypervisorType == HypervisorType.XenServer || hypervisorType == HypervisorType.VMware || hypervisorType == HypervisorType.Simulator)) { + throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override"); + } + } + private void checkIfHostNameUniqueInNtwkDomain(String hostName, List networkList) { // Check that hostName is unique in the network domain Map> ntwkDomains = new HashMap>(); diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index f9f91d15d1a8..da59f07a3a86 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -32,11 +32,19 @@ import java.util.List; import java.util.Map; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -81,6 +89,9 @@ public class UserVmManagerImplTest { @Mock private ServiceOfferingDao _serviceOfferingDao; + @Mock + private DiskOfferingDao diskOfferingDao; + @Mock private ServiceOfferingVO serviceOfferingVO; @@ -131,8 +142,18 @@ public class UserVmManagerImplTest { @Mock private UserVO callerUser; + @Mock + private VMTemplateDao templateDao; + private long vmId = 1l; + private static final long GiB_TO_BYTES = 1024 * 1024 * 1024; + + private Map customParameters = new HashMap<>(); + + private DiskOfferingVO smallerDisdkOffering = prepareDiskOffering(5l * GiB_TO_BYTES, 1l, 1L, 2L); + private DiskOfferingVO largerDisdkOffering = prepareDiskOffering(10l * GiB_TO_BYTES, 2l, 10L, 20L); + @Before public void beforeTest() { @@ -144,6 +165,8 @@ public void beforeTest() { Mockito.when(callerAccount.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); CallContext.register(callerUser, callerAccount); + + customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, "123"); } @After @@ -380,4 +403,159 @@ public void testValidatekeyValuePair() throws Exception { assertTrue(userVmManagerImpl.isValidKeyValuePair("param:key-2=value2")); assertTrue(userVmManagerImpl.isValidKeyValuePair("my.config.v0=False")); } + + @Test + public void configureCustomRootDiskSizeTest() { + String vmDetailsRootDiskSize = "123"; + Map customParameters = new HashMap<>(); + customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, vmDetailsRootDiskSize); + long expectedRootDiskSize = 123l * GiB_TO_BYTES; + long offeringRootDiskSize = 0l; + prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); + } + + @Test(expected = InvalidParameterValueException.class) + public void configureCustomRootDiskSizeTestExpectExceptionZero() { + String vmDetailsRootDiskSize = "0"; + Map customParameters = new HashMap<>(); + customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, vmDetailsRootDiskSize); + long expectedRootDiskSize = 0l; + long offeringRootDiskSize = 0l; + prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); + } + + @Test(expected = InvalidParameterValueException.class) + public void configureCustomRootDiskSizeTestExpectExceptionNegativeNum() { + String vmDetailsRootDiskSize = "-123"; + Map customParameters = new HashMap<>(); + customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, vmDetailsRootDiskSize); + long expectedRootDiskSize = -123l * GiB_TO_BYTES; + long offeringRootDiskSize = 0l; + prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); + } + + @Test + public void configureCustomRootDiskSizeTestEmptyParameters() { + Map customParameters = new HashMap<>(); + long expectedRootDiskSize = 99l * GiB_TO_BYTES; + long offeringRootDiskSize = 0l; + prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); + } + + @Test + public void configureCustomRootDiskSizeTestEmptyParametersAndOfferingRootSize() { + Map customParameters = new HashMap<>(); + long expectedRootDiskSize = 10l * GiB_TO_BYTES; + long offeringRootDiskSize = 10l * GiB_TO_BYTES;; + + prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); + } + + private void prepareAndRunConfigureCustomRootDiskSizeTest(Map customParameters, long expectedRootDiskSize, int timesVerifyIfHypervisorSupports, Long offeringRootDiskSize) { + 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); + + Assert.assertEquals(expectedRootDiskSize, rootDiskSize); + Mockito.verify(userVmManagerImpl, Mockito.times(timesVerifyIfHypervisorSupports)).verifyIfHypervisorSupportsRootdiskSizeOverride(Mockito.any()); + } + + @Test + public void verifyIfHypervisorSupportRootdiskSizeOverrideTest() { + Hypervisor.HypervisorType[] hypervisorTypeArray = Hypervisor.HypervisorType.values(); + int exceptionCounter = 0; + int expectedExceptionCounter = hypervisorTypeArray.length - 4; + + for(int i = 0; i < hypervisorTypeArray.length; i++) { + if (Hypervisor.HypervisorType.KVM == hypervisorTypeArray[i] + || Hypervisor.HypervisorType.XenServer == hypervisorTypeArray[i] + || Hypervisor.HypervisorType.VMware == hypervisorTypeArray[i] + || Hypervisor.HypervisorType.Simulator == hypervisorTypeArray[i]) { + userVmManagerImpl.verifyIfHypervisorSupportsRootdiskSizeOverride(hypervisorTypeArray[i]); + } else { + try { + userVmManagerImpl.verifyIfHypervisorSupportsRootdiskSizeOverride(hypervisorTypeArray[i]); + } catch (InvalidParameterValueException e) { + exceptionCounter ++; + } + } + } + + Assert.assertEquals(expectedExceptionCounter, exceptionCounter); + } + + @Test (expected = InvalidParameterValueException.class) + public void prepareResizeVolumeCmdTestRootVolumeNull() { + DiskOfferingVO newRootDiskOffering = Mockito.mock(DiskOfferingVO.class); + DiskOfferingVO currentRootDiskOffering = Mockito.mock(DiskOfferingVO.class); + userVmManagerImpl.prepareResizeVolumeCmd(null, currentRootDiskOffering, newRootDiskOffering); + } + + @Test (expected = InvalidParameterValueException.class) + public void prepareResizeVolumeCmdTestCurrentRootDiskOffering() { + DiskOfferingVO newRootDiskOffering = Mockito.mock(DiskOfferingVO.class); + VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class); + userVmManagerImpl.prepareResizeVolumeCmd(rootVolumeOfVm, null, newRootDiskOffering); + } + + @Test (expected = InvalidParameterValueException.class) + public void prepareResizeVolumeCmdTestNewRootDiskOffering() { + VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class); + DiskOfferingVO currentRootDiskOffering = Mockito.mock(DiskOfferingVO.class); + userVmManagerImpl.prepareResizeVolumeCmd(rootVolumeOfVm, currentRootDiskOffering, null); + } + + @Test + public void prepareResizeVolumeCmdTestNewOfferingLarger() { + prepareAndRunResizeVolumeTest(2L, 10L, 20L, smallerDisdkOffering, largerDisdkOffering); + } + + @Test + public void prepareResizeVolumeCmdTestSameOfferingSize() { + prepareAndRunResizeVolumeTest(null, 1L, 2L, smallerDisdkOffering, smallerDisdkOffering); + } + + @Test + public void prepareResizeVolumeCmdTestOfferingRootSizeZero() { + DiskOfferingVO rootSizeZero = prepareDiskOffering(0l, 3l, 100L, 200L); + prepareAndRunResizeVolumeTest(null, 100L, 200L, smallerDisdkOffering, rootSizeZero); + } + + @Test (expected = InvalidParameterValueException.class) + public void prepareResizeVolumeCmdTestNewOfferingSmaller() { + prepareAndRunResizeVolumeTest(2L, 10L, 20L, largerDisdkOffering, smallerDisdkOffering); + } + + private void prepareAndRunResizeVolumeTest(Long expectedOfferingId, long expectedMinIops, long expectedMaxIops, DiskOfferingVO currentRootDiskOffering, DiskOfferingVO newRootDiskOffering) { + long rootVolumeId = 1l; + VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class); + Mockito.when(rootVolumeOfVm.getId()).thenReturn(rootVolumeId); + + ResizeVolumeCmd resizeVolumeCmd = userVmManagerImpl.prepareResizeVolumeCmd(rootVolumeOfVm, currentRootDiskOffering, newRootDiskOffering); + + Assert.assertEquals(rootVolumeId, resizeVolumeCmd.getId().longValue()); + Assert.assertEquals(expectedOfferingId, resizeVolumeCmd.getNewDiskOfferingId()); + Assert.assertEquals(expectedMinIops, resizeVolumeCmd.getMinIops().longValue()); + Assert.assertEquals(expectedMaxIops, resizeVolumeCmd.getMaxIops().longValue()); + } + + private DiskOfferingVO prepareDiskOffering(long rootSize, long diskOfferingId, long offeringMinIops, long offeringMaxIops) { + DiskOfferingVO newRootDiskOffering = Mockito.mock(DiskOfferingVO.class); + Mockito.when(newRootDiskOffering.getDiskSize()).thenReturn(rootSize); + Mockito.when(newRootDiskOffering.getId()).thenReturn(diskOfferingId); + Mockito.when(newRootDiskOffering.getMinIops()).thenReturn(offeringMinIops); + Mockito.when(newRootDiskOffering.getMaxIops()).thenReturn(offeringMaxIops); + Mockito.when(newRootDiskOffering.getName()).thenReturn("OfferingName"); + return newRootDiskOffering; + } } diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 4ce1f379b8c9..c20c4ac50d08 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -437,6 +437,14 @@ isBoolean: true, isChecked: false }, + rootDiskSize: { + label: 'label.root.disk.size', + docID: 'helpRootDiskSizeGb', + validation: { + required: false, + number: true + } + }, storageTags: { label: 'label.storage.tags', docID: 'helpComputeOfferingStorageType', @@ -873,6 +881,12 @@ offerha: (args.data.offerHA == "on") }); + if (args.data.rootDiskSize != null && args.data.rootDiskSize > 0) { + $.extend(data, { + rootDiskSize: args.data.rootDiskSize + }); + } + if (args.data.storageTags != null && args.data.storageTags.length > 0) { $.extend(data, { tags: args.data.storageTags diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index a801a6b52e0b..504a7a25452f 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -1066,6 +1066,10 @@ cloudStack.docs = { desc: 'Volume size in GB (1GB = 1,073,741,824 bytes)', externalLink: '' }, + helpRootDiskSizeGb: { + desc: 'Root disk size in GB', + externalLink: '' + }, // Add VPC helpVPCName: { desc: 'A name for the new VPC',