Skip to content
2 changes: 1 addition & 1 deletion api/src/main/java/com/cloud/server/ManagementService.java
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ public interface ManagementService {
*/
Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(Long volumeId);

Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolumeInternal(Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck);
Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForSystemMigrationOfVolume(Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck);

String[] listEventTypes();

Expand Down
21 changes: 14 additions & 7 deletions server/src/main/java/com/cloud/server/ManagementServerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1526,7 +1526,7 @@ private boolean hasSuitablePoolsForVolume(final VolumeVO volume, final Host host
@Override
public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(final Long volumeId) {

Pair<List<? extends StoragePool>, List<? extends StoragePool>> allPoolsAndSuitablePoolsPair = listStoragePoolsForMigrationOfVolumeInternal(volumeId, null, null, null, null, false, true);
Pair<List<? extends StoragePool>, List<? extends StoragePool>> allPoolsAndSuitablePoolsPair = listStoragePoolsForMigrationOfVolumeInternal(volumeId, null, null, null, null, false, true, false);
List<? extends StoragePool> allPools = allPoolsAndSuitablePoolsPair.first();
List<? extends StoragePool> suitablePools = allPoolsAndSuitablePoolsPair.second();
List<StoragePool> avoidPools = new ArrayList<>();
Expand All @@ -1542,13 +1542,20 @@ public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStorag
return new Pair<List<? extends StoragePool>, List<? extends StoragePool>>(allPools, suitablePools);
}

public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolumeInternal(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck) {
final Account caller = getCaller();
if (!_accountMgr.isRootAdmin(caller.getId())) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Caller is not a root admin, permission denied to migrate the volume");
@Override
public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForSystemMigrationOfVolume(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck) {
return listStoragePoolsForMigrationOfVolumeInternal(volumeId, newDiskOfferingId, newSize, newMinIops, newMaxIops, keepSourceStoragePool, bypassStorageTypeCheck, true);
}

public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolumeInternal(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck, boolean bypassAccountCheck) {
if (!bypassAccountCheck) {
final Account caller = getCaller();
if (!_accountMgr.isRootAdmin(caller.getId())) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Caller is not a root admin, permission denied to migrate the volume");
}
throw new PermissionDeniedException("No permission to migrate volume, only root admin can migrate a volume");
}
throw new PermissionDeniedException("No permission to migrate volume, only root admin can migrate a volume");
}

final VolumeVO volume = _volumeDao.findById(volumeId);
Expand Down
20 changes: 18 additions & 2 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
Expand Down Expand Up @@ -1767,14 +1768,14 @@ private Volume changeDiskOfferingForVolumeInternal(VolumeVO volume, Long newDisk
return volume;
}

if (currentSize != newSize || newMaxIops != volume.getMaxIops() || newMinIops != volume.getMinIops()) {
if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(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, false);
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), newSize, newMinIops, newMaxIops, true, false);
List<? extends StoragePool> suitableStoragePools = poolsPair.second();

if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) {
Expand Down Expand Up @@ -1823,6 +1824,21 @@ private Volume changeDiskOfferingForVolumeInternal(VolumeVO volume, Long newDisk
return volume;
}

/**
* This method is to compare long values, in miniops and maxiops a or b can be null or 0.
* Use this method to treat 0 and null as same
*
* @param a
* @param b
* @return true if a and b are equal excluding 0 and null values.
*/
private boolean compareEqualsIncludingNullOrZero(Long a, Long b) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shwstppr, what do you think about renaming the method to compareLongsDefaultingNullToZero?

a = ObjectUtils.defaultIfNull(a, 0L);
b = ObjectUtils.defaultIfNull(b, 0L);

return a.equals(b);
}
Comment on lines +1835 to +1840

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shwstppr, we could move this method to com.cloud.utils.NumbersUtils and add some unit tests.


/**
* Returns true if the new disk offering is the same than current offering, and the respective Service offering is a custom (constraint or unconstraint) offering.
*/
Expand Down
3 changes: 3 additions & 0 deletions server/src/main/java/com/cloud/vm/UserVmManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@
*/
public interface UserVmManager extends UserVmService {
String EnableDynamicallyScaleVmCK = "enable.dynamic.scale.vm";
String AllowDiskOfferingChangeDuringScaleVmCK = "allow.diskoffering.change.during.scale.vm";
String AllowUserExpungeRecoverVmCK ="allow.user.expunge.recover.vm";
ConfigKey<Boolean> EnableDynamicallyScaleVm = new ConfigKey<Boolean>("Advanced", Boolean.class, EnableDynamicallyScaleVmCK, "false",
"Enables/Disables dynamically scaling a vm", true, ConfigKey.Scope.Zone);
ConfigKey<Boolean> AllowDiskOfferingChangeDuringScaleVm = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowDiskOfferingChangeDuringScaleVmCK, "false",
"Determines whether to allow or disallow disk offering change for root volume during scaling of a stopped or running vm", true, ConfigKey.Scope.Zone);
ConfigKey<Boolean> AllowUserExpungeRecoverVm = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowUserExpungeRecoverVmCK, "false",
"Determines whether users can expunge or recover their vm", true, ConfigKey.Scope.Account);
ConfigKey<Boolean> DisplayVMOVFProperties = new ConfigKey<Boolean>("Advanced", Boolean.class, "vm.display.ovf.properties", "false",
Expand Down
15 changes: 11 additions & 4 deletions server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ private UserVm upgradeStoppedVirtualMachine(Long vmId, Long svcOffId, Map<String

// resize and migrate the root volume if required
DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newServiceOffering.getDiskOfferingId());
changeDiskOfferingForRootVolume(vmId, newDiskOffering, customParameters);
changeDiskOfferingForRootVolume(vmId, newDiskOffering, customParameters, vmInstance.getDataCenterId());

_itMgr.upgradeVmDb(vmId, newServiceOffering, currentServiceOffering);

Expand Down Expand Up @@ -2000,7 +2000,7 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI

// #3 resize or migrate the root volume if required
DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newServiceOffering.getDiskOfferingId());
changeDiskOfferingForRootVolume(vmId, newDiskOffering, customParameters);
changeDiskOfferingForRootVolume(vmId, newDiskOffering, customParameters, vmInstance.getDataCenterId());

// #4 scale the vm now
vmInstance = _vmInstanceDao.findById(vmId);
Expand Down Expand Up @@ -2036,7 +2036,14 @@ private void validateDiskOfferingChecks(ServiceOfferingVO currentServiceOffering
}
}

private void changeDiskOfferingForRootVolume(Long vmId, DiskOfferingVO newDiskOffering, Map<String, String> customParameters) throws ResourceAllocationException {
private void changeDiskOfferingForRootVolume(Long vmId, DiskOfferingVO newDiskOffering, Map<String, String> customParameters, Long zoneId) throws ResourceAllocationException {

if (!AllowDiskOfferingChangeDuringScaleVm.valueIn(zoneId)) {
if (s_logger.isDebugEnabled()) {
s_logger.debug(String.format("Changing the disk offering of the root volume during the compute offering change operation is disabled. Please check the setting [%s].", AllowDiskOfferingChangeDuringScaleVm.key()));
}
return;
}

List<VolumeVO> vols = _volsDao.findReadyAndAllocatedRootVolumesByInstance(vmId);

Expand Down Expand Up @@ -7791,7 +7798,7 @@ public String getConfigComponentName() {

@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {EnableDynamicallyScaleVm, AllowUserExpungeRecoverVm, VmIpFetchWaitInterval, VmIpFetchTrialMax,
return new ConfigKey<?>[] {EnableDynamicallyScaleVm, AllowDiskOfferingChangeDuringScaleVm, AllowUserExpungeRecoverVm, VmIpFetchWaitInterval, VmIpFetchTrialMax,
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ private UserVm migrateImportedVM(HostVO sourceHost, VirtualMachineTemplate templ
continue;
}
LOGGER.debug(String.format("Volume %s needs to be migrated", volumeVO.getUuid()));
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForMigrationOfVolumeInternal(profile.getVolumeId(), null, null, null, null, false, true);
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(profile.getVolumeId(), null, null, null, null, false, true);
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
cleanupFailedImportVM(vm);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during volume ID: %s migration as no suitable pool(s) found", userVm.getInstanceName(), volumeVO.getUuid()));
Expand Down
Loading