You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2020/10/30 15:56:25 UTC

[cloudstack] branch master updated: Allow to configure root disk size via Service Offering (diskoffering of type Service). (#4341)

This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/master by this push:
     new b3a1cb4  Allow to configure root disk size via Service Offering (diskoffering of type Service). (#4341)
b3a1cb4 is described below

commit b3a1cb41c81bec1ee661a87741451a54daa83509
Author: Gabriel Beims Bräscher <ga...@apache.org>
AuthorDate: Fri Oct 30 12:56:11 2020 -0300

    Allow to configure root disk size via Service Offering (diskoffering of type Service). (#4341)
---
 .../admin/offering/CreateServiceOfferingCmd.java   |   7 +
 .../api/command/user/volume/ResizeVolumeCmd.java   |   7 +
 .../api/response/ServiceOfferingResponse.java      |   7 +
 .../resources/META-INF/db/schema-41400to41500.sql  |   2 +-
 .../api/query/dao/ServiceOfferingJoinDaoImpl.java  |   4 +
 .../cloud/api/query/vo/ServiceOfferingJoinVO.java  |   7 +-
 .../configuration/ConfigurationManagerImpl.java    |  12 +-
 .../com/cloud/storage/VolumeApiServiceImpl.java    |  29 ++--
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 114 +++++++++----
 .../java/com/cloud/vm/UserVmManagerImplTest.java   | 178 +++++++++++++++++++++
 ui/scripts/configuration.js                        |  14 ++
 ui/scripts/docs.js                                 |   4 +
 12 files changed, 345 insertions(+), 40 deletions(-)

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 745e6a0..3219422 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 class CreateServiceOfferingCmd extends BaseCmd {
         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 2471c80..1f1bc6a 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 class ResizeVolumeCmd extends BaseAsyncCmd implements UserCmd {
         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 e13735b..05fcfbd 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 class ServiceOfferingResponse extends BaseResponse {
         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 e87ac07..0c3bb09 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
@@ -406,6 +406,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`,
@@ -821,7 +822,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 4c025b9..87b0374 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 class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
         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 class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
                     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 e025da5..4d9b2cd 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 class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit
 
     public Long getIopsWriteRateMaxLength() { return iopsWriteRateMaxLength; }
 
-
     public boolean isDynamic() {
         return cpu == null || speed == null || ramSize == null;
     }
@@ -392,4 +394,7 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit
         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 9b272d6..028e009 100755
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -432,6 +432,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
 
     private static final Set<Provider> 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<String, Object> params) throws ConfigurationException {
         final String maxVolumeSizeInGbString = _configDao.getValue(Config.MaxVolumeSize.key());
@@ -2473,7 +2475,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
 
         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(),
@@ -2484,7 +2486,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
     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<Long> domainIds, List<Long> zoneIds, final String hostTag,
-            final Integer networkRate, final String deploymentPlanner, final Map<String, String> details, final Boolean isCustomizedIops, Long minIops, Long maxIops,
+            final Integer networkRate, final String deploymentPlanner, final Map<String, String> 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,
@@ -2545,6 +2547,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
             }
         }
 
+        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 ad3dd30..be2d52e 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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
         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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
 
         // 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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
                 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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
 
             _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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
                 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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
                 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 a931159..1aa452c 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
         // Check that the specified service offering ID is valid
         _itMgr.checkIfCanUpgrade(vmInstance, newServiceOffering);
 
-        DiskOfferingVO newROOTDiskOffering = _diskOfferingDao.findById(newServiceOffering.getId());
+        // 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()));
+
+        DiskOfferingVO newRootDiskOffering = _diskOfferingDao.findById(newServiceOffering.getId());
 
         List<VolumeVO> 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
 
     }
 
+    /**
+     * Prepares the Resize Volume Command and verifies if the disk offering from the new service offering can be resized.
+     * <br>
+     * 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 @@ 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 = 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
         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<String, String> 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<? extends Network> networkList) {
         // Check that hostName is unique in the network domain
         Map<String, List<Long>> ntwkDomains = new HashMap<String, List<Long>>();
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index f9f91d1..da59f07 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.HashMap;
 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;
@@ -82,6 +90,9 @@ public class UserVmManagerImplTest {
     private ServiceOfferingDao _serviceOfferingDao;
 
     @Mock
+    private DiskOfferingDao diskOfferingDao;
+
+    @Mock
     private ServiceOfferingVO serviceOfferingVO;
 
     @Mock
@@ -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<String, String> 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 class UserVmManagerImplTest {
 
         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 class UserVmManagerImplTest {
         assertTrue(userVmManagerImpl.isValidKeyValuePair("param:key-2=value2"));
         assertTrue(userVmManagerImpl.isValidKeyValuePair("my.config.v0=False"));
     }
+
+    @Test
+    public void configureCustomRootDiskSizeTest() {
+        String vmDetailsRootDiskSize = "123";
+        Map<String, String> 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<String, String> 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<String, String> 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<String, String> customParameters = new HashMap<>();
+        long expectedRootDiskSize = 99l * GiB_TO_BYTES;
+        long offeringRootDiskSize = 0l;
+        prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize);
+    }
+
+    @Test
+    public void configureCustomRootDiskSizeTestEmptyParametersAndOfferingRootSize() {
+        Map<String, String> customParameters = new HashMap<>();
+        long expectedRootDiskSize = 10l * GiB_TO_BYTES;
+        long offeringRootDiskSize = 10l * GiB_TO_BYTES;;
+
+        prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize);
+    }
+
+    private void prepareAndRunConfigureCustomRootDiskSizeTest(Map<String, String> 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 4ce1f37..c20c4ac 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 a801a6b..504a7a2 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',