This is an automated email from the ASF dual-hosted git repository. sureshanaparti pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 208ae84dd760aa7e18077ad063318df237fb23dc Merge: 2c11171 da56a2a Author: Suresh Kumar Anaparti <[email protected]> AuthorDate: Tue Feb 8 19:01:34 2022 +0530 Merge branch '4.16' into main .../java/com/cloud/storage/VolumeApiService.java | 2 + .../java/org/apache/cloudstack/api/BaseCmd.java | 2 +- .../service/VolumeOrchestrationService.java | 7 + .../cloud/entity/api/VMEntityManagerImpl.java | 2 +- .../engine/orchestration/VolumeOrchestrator.java | 23 +-- framework/managed-context/pom.xml | 6 +- .../apache/cloudstack/quota/QuotaManagerImpl.java | 33 ++- .../cloudstack/quota/QuotaManagerImplTest.java | 8 +- .../affinity/ExplicitDedicationProcessor.java | 6 + plugins/alert-handlers/snmp-alerts/pom.xml | 4 +- plugins/alert-handlers/syslog-alerts/pom.xml | 4 +- .../cloud/deploy/ImplicitDedicationPlanner.java | 2 +- plugins/hypervisors/ovm3/pom.xml | 4 +- plugins/integrations/kubernetes-service/pom.xml | 6 +- plugins/network-elements/juniper-contrail/pom.xml | 6 + plugins/user-authenticators/ldap/pom.xml | 4 + pom.xml | 20 +- .../main/java/com/cloud/configuration/Config.java | 18 +- .../configuration/ConfigurationManagerImpl.java | 18 +- .../deploy/DeploymentPlanningManagerImpl.java | 19 +- .../com/cloud/storage/VolumeApiServiceImpl.java | 20 +- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 33 +-- .../java/com/cloud/vm/UserVmManagerImplTest.java | 23 ++- services/console-proxy/server/pom.xml | 4 +- services/secondary-storage/server/pom.xml | 4 +- test/pom.xml | 4 +- ui/public/locales/en.json | 3 +- .../view/InstanceNicsNetworkSelectListView.vue | 149 ++++++++++++++ ui/src/components/view/NicNetworkSelectForm.vue | 228 +++++++++++++++++++++ ui/src/views/compute/DeployVM.vue | 62 +++--- ui/src/views/offering/AddComputeOffering.vue | 2 +- utils/pom.xml | 8 +- 32 files changed, 583 insertions(+), 151 deletions(-) diff --cc api/src/main/java/com/cloud/storage/VolumeApiService.java index 516fe77,f7543b7..84559e6 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@@ -154,5 -153,5 +154,7 @@@ public interface VolumeApiService Volume recoverVolume(long volumeId); + boolean validateVolumeSizeInBytes(long size); ++ + Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException; } diff --cc engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 3391532,9c1e998..a311912 --- 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 @@@ -83,9 -78,8 +78,9 @@@ import org.apache.cloudstack.storage.da import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; - import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.to.DataTO; diff --cc server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 7d98dfb,e5347c4..0b4fcf8 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@@ -268,8 -269,9 +268,9 @@@ import com.cloud.vm.dao.VMInstanceDao import com.google.common.base.Enums; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; -import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Sets; + import com.googlecode.ipv6.IPv6Address; public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable { public static final Logger s_logger = Logger.getLogger(ConfigurationManagerImpl.class); @@@ -3004,72 -3022,6 +3001,76 @@@ _serviceOfferingDetailsDao.saveDetails(detailsVO); } + 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<Long> domainIds, List<Long> zoneIds, final String hostTag, + 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, + 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 maxVolumeSizeInGb = VolumeOrchestrationService.MaxVolumeSize.value(); ++ if (rootDiskSizeInGiB > maxVolumeSizeInGb) { ++ throw new InvalidParameterValueException(String.format("The maximum size for a disk is %d GB.", maxVolumeSizeInGb)); ++ } + 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<DiskOfferingDetailVO> diskDetailsVO = new ArrayList<DiskOfferingDetailVO>(); // Support disk offering details for below parameters @@@ -3311,12 -3263,13 +3312,13 @@@ 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<String, String> details, final Long storagePolicyID) { + final Integer hypervisorSnapshotReserve, String cacheMode, final Map<String, String> details, final Long storagePolicyID, final boolean diskSizeStrictness) { long diskSize = 0;// special case for custom disk offerings + long maxVolumeSizeInGb = VolumeOrchestrationService.MaxVolumeSize.value(); if (numGibibytes != null && numGibibytes <= 0) { - throw new InvalidParameterValueException("Please specify a disk size of at least 1 Gb."); - } else if (numGibibytes != null && numGibibytes > _maxVolumeSizeInGb) { - throw new InvalidParameterValueException("The maximum size for a disk is " + _maxVolumeSizeInGb + " Gb."); + throw new InvalidParameterValueException("Please specify a disk size of at least 1 GB."); + } else if (numGibibytes != null && numGibibytes > maxVolumeSizeInGb) { + throw new InvalidParameterValueException(String.format("The maximum size for a disk is %d GB.", maxVolumeSizeInGb)); } final ProvisioningType typedProvisioningType = ProvisioningType.getProvisioningType(provisioningType); diff --cc server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index ce7f35a,5912ff9..e42ea43 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@@ -322,10 -311,6 +322,9 @@@ public class VolumeApiServiceImpl exten public static final ConfigKey<Boolean> AllowUserExpungeRecoverVolume = new ConfigKey<Boolean>("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<Boolean> MatchStoragePoolTagsWithDiskOffering = new ConfigKey<Boolean>("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<Volume.State, Volume.Event, Volume> _volStateMachine; private static final Set<Volume.State> STATES_VOLUME_CANNOT_BE_DESTROYED = new HashSet<>(Arrays.asList(Volume.State.Destroy, Volume.State.Expunging, Volume.State.Expunged, Volume.State.Allocated)); @@@ -1668,349 -1634,6 +1668,349 @@@ } @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<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForMigrationOfVolumeInternal(volume.getId(), newDiskOffering.getId(), newSize, newMinIops, newMaxIops, true); + List<? extends StoragePool> 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<Volume> 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<SnapshotVO> 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)) { ++ if (!validateVolumeSizeInBytes(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) { return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId()); diff --cc server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 275f701,58ea4e9..d553ab4 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@@ -3802,28 -3830,30 +3801,28 @@@ public class UserVmManagerImpl extends // check if account/domain is with in resource limits to create a new vm boolean isIso = Storage.ImageFormat.ISO == template.getFormat(); - long volumesSize = configureCustomRootDiskSize(customParameters, template, hypervisorType, offering); - - if (diskOfferingId != null) { - long size = 0; - DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); - if (diskOffering == null) { - throw new InvalidParameterValueException("Specified disk offering cannot be found"); - } - if (diskOffering.isCustomized()) { - 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); + 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."); } - size = diskSize * GiB_TO_BYTES; } else { - size = diskOffering.getDiskSize(); + rootDiskOfferingId = diskOfferingId; + diskOfferingId = null; } - _volumeService.validateVolumeSizeInBytes(size); - volumesSize += size; + } + if (!offering.getDiskOfferingStrictness() && overrideDiskOfferingId != null) { + rootDiskOfferingId = overrideDiskOfferingId; + } + + DiskOfferingVO rootdiskOffering = _diskOfferingDao.findById(rootDiskOfferingId); - long size = configureCustomRootDiskSize(customParameters, template, hypervisorType, rootdiskOffering); ++ long volumesSize = configureCustomRootDiskSize(customParameters, template, hypervisorType, rootdiskOffering); + + if (!isIso && diskOfferingId != null) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); - size += verifyAndGetDiskSize(diskOffering, diskSize); ++ volumesSize += verifyAndGetDiskSize(diskOffering, diskSize); } if (! VirtualMachineManager.ResourceCountRunningVMsonly.value()) { resourceLimitCheck(owner, isDisplayVm, new Long(offering.getCpu()), new Long(offering.getRamSize())); @@@ -4134,24 -4164,6 +4133,29 @@@ return vm; } + private long verifyAndGetDiskSize(DiskOfferingVO diskOffering, Long diskSize) { + long size = 0l; - if (diskOffering != null && diskOffering.isCustomized() && !diskOffering.isComputeOnly()) { ++ if (diskOffering == null) { ++ throw new InvalidParameterValueException("Specified disk offering cannot be found"); ++ } ++ if (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 = diskSize * GiB_TO_BYTES; ++ } else { ++ size = diskOffering.getDiskSize(); + } - size += diskOffering.getDiskSize(); ++ _volumeService.validateVolumeSizeInBytes(size); + 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); @@@ -4166,10 -4178,12 +4170,11 @@@ * 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) { + protected long configureCustomRootDiskSize(Map<String, String> 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 + _volumeService.validateVolumeSizeInBytes(rootDiskSizeInBytes); long rootDiskSizeInGiB = rootDiskSizeInBytes / GiB_TO_BYTES; customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, String.valueOf(rootDiskSizeInGiB)); return rootDiskSizeInBytes; diff --cc server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index c9c07c4,c9460c3..0660388 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@@ -71,8 -64,15 +64,14 @@@ import com.cloud.network.dao.NetworkVO import com.cloud.offering.ServiceOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; + import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOSVO; -import com.cloud.storage.Storage; + import com.cloud.storage.VMTemplateVO; + import com.cloud.storage.VolumeApiService; + import com.cloud.storage.VolumeVO; + import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSDao; + import com.cloud.storage.dao.VMTemplateDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; @@@ -474,7 -483,9 +478,8 @@@ public class UserVmManagerImplTest Mockito.when(diskfferingVo.getDiskSize()).thenReturn(offeringRootDiskSize); + Mockito.when(volumeApiService.validateVolumeSizeInBytes(Mockito.anyLong())).thenReturn(true); - 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());
