Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resize volume: add pool capacity disablethreshold for resize and allow volume auto migration #9761

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
description = "new disk offering id")
private Long newDiskOfferingId;

@Parameter(name = ApiConstants.AUTO_MIGRATE, type = CommandType.BOOLEAN, required = false,
description = "Flag to allow automatic migration of the volume to another suitable storage pool that accommodates the new size", since = "4.20.1")
private Boolean autoMigrate;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -129,6 +133,10 @@
return newDiskOfferingId;
}

public boolean getAutoMigrate() {

Check warning on line 136 in api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java#L136

Added line #L136 was not covered by tests
return autoMigrate == null ? false : autoMigrate;
}

Check warning on line 138 in api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java#L138

Added line #L138 was not covered by tests

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public interface CapacityManager {
static final String StorageCapacityDisableThresholdCK = "pool.storage.capacity.disablethreshold";
static final String StorageOverprovisioningFactorCK = "storage.overprovisioning.factor";
static final String StorageAllocatedCapacityDisableThresholdCK = "pool.storage.allocated.capacity.disablethreshold";
static final String StorageAllocatedCapacityDisableThresholdForVolumeResizeCK = "pool.storage.allocated.resize.capacity.disablethreshold";

static final ConfigKey<Float> CpuOverprovisioningFactor =
new ConfigKey<>(
Expand Down Expand Up @@ -118,6 +119,17 @@ public interface CapacityManager {
"Percentage (as a value between 0 and 1) of secondary storage capacity threshold.",
true);

static final ConfigKey<Double> StorageAllocatedCapacityDisableThresholdForVolumeSize =
new ConfigKey<>(
ConfigKey.CATEGORY_ALERT,
Double.class,
StorageAllocatedCapacityDisableThresholdForVolumeResizeCK,
"0.90",
"Percentage (as a value between 0 and 1) of allocated storage utilization above which allocators will disable using the pool for volume resize. " +
"This is applicable only when volume.resize.allowed.beyond.allocation is set to true.",
true,
ConfigKey.Scope.Zone);

public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId);

void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ public interface StorageManager extends StorageService {
ConfigKey<Long> HEURISTICS_SCRIPT_TIMEOUT = new ConfigKey<>("Advanced", Long.class, "heuristics.script.timeout", "3000",
"The maximum runtime, in milliseconds, to execute the heuristic rule; if it is reached, a timeout will happen.", true);

ConfigKey<Boolean> AllowVolumeReSizeBeyondAllocation = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false",
"Determines whether volume size can exceed the pool capacity allocation disable threshold (pool.storage.allocated.capacity.disablethreshold) " +
"when resize a volume upto resize capacity disable threshold (pool.storage.allocated.resize.capacity.disablethreshold)",
true, ConfigKey.Scope.Zone);

/**
* should we execute in sequence not involving any storages?
* @return tru if commands should execute in sequence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,7 @@ public String getConfigComponentName() {
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor,
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold};
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold,
StorageAllocatedCapacityDisableThresholdForVolumeSize };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ protected void weightBasedParametersForValidation() {
weightBasedParametersForValidation.add(Config.LocalStorageCapacityThreshold.key());
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThreshold.key());
weightBasedParametersForValidation.add(CapacityManager.StorageCapacityDisableThreshold.key());
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key());
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterCPUCapacityDisableThreshold.key());
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterMemoryCapacityDisableThreshold.key());
weightBasedParametersForValidation.add(Config.AgentLoadThreshold.key());
Expand Down
25 changes: 21 additions & 4 deletions server/src/main/java/com/cloud/storage/StorageManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3101,7 +3101,7 @@
} else {
final StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
final long allocatedSizeWithTemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null);
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize);
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);

Check warning on line 3104 in server/src/main/java/com/cloud/storage/StorageManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/StorageManagerImpl.java#L3104

Added line #L3104 was not covered by tests
}
}

Expand Down Expand Up @@ -3164,6 +3164,10 @@
}

protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize) {
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, false);
}

Check warning on line 3168 in server/src/main/java/com/cloud/storage/StorageManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/StorageManagerImpl.java#L3167-L3168

Added lines #L3167 - L3168 were not covered by tests

protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize, boolean forVolumeResize) {
// allocated space includes templates
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());

Expand Down Expand Up @@ -3196,10 +3200,22 @@
if (usedPercentage > storageAllocatedThreshold) {
if (logger.isDebugEnabled()) {
logger.debug("Insufficient un-allocated capacity on: " + pool.getId() + " for storage allocation since its allocated percentage: " + usedPercentage
+ " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + storageAllocatedThreshold + ", skipping this pool");
+ " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + storageAllocatedThreshold);
}
if (!forVolumeResize) {
return false;
}
if (!AllowVolumeReSizeBeyondAllocation.valueIn(pool.getDataCenterId())) {
logger.debug(String.format("Skipping the pool %s as %s is false", pool, AllowVolumeReSizeBeyondAllocation.key()));
return false;
}

return false;
double storageAllocatedThresholdForResize = CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.valueIn(pool.getDataCenterId());
if (usedPercentage > storageAllocatedThresholdForResize) {
logger.debug(String.format("Skipping the pool %s since its allocated percentage: %s has crossed the allocated %s: %s",
pool, usedPercentage, CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key(), storageAllocatedThresholdForResize));
return false;
}
}

if (totalOverProvCapacity < (allocatedSizeWithTemplate + totalAskingSize)) {
Expand Down Expand Up @@ -4050,7 +4066,8 @@
MountDisabledStoragePool,
VmwareCreateCloneFull,
VmwareAllowParallelExecution,
DataStoreDownloadFollowRedirects
DataStoreDownloadFollowRedirects,
AllowVolumeReSizeBeyondAllocation
};
}

Expand Down
68 changes: 60 additions & 8 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@
Long newMaxIops = cmd.getMaxIops();
Integer newHypervisorSnapshotReserve = null;
boolean shrinkOk = cmd.isShrinkOk();
boolean autoMigrateVolume = cmd.getAutoMigrate();

VolumeVO volume = _volsDao.findById(cmd.getEntityId());
if (volume == null) {
Expand Down Expand Up @@ -1154,8 +1155,6 @@
newSize = volume.getSize();
}

newMinIops = cmd.getMinIops();

if (newMinIops != null) {
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter.");
Expand All @@ -1165,8 +1164,6 @@
newMinIops = volume.getMinIops();
}

newMaxIops = cmd.getMaxIops();

if (newMaxIops != null) {
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter.");
Expand Down Expand Up @@ -1288,6 +1285,54 @@
return volume;
}

Long newDiskOfferingId = newDiskOffering != null ? newDiskOffering.getId() : diskOffering.getId();

boolean volumeMigrateRequired = false;
List<? extends StoragePool> suitableStoragePoolsWithEnoughSpace = null;
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) {
if (!autoMigrateVolume) {
throw new CloudRuntimeException(String.format("Failed to resize volume %s since the storage pool does not have enough space to accommodate new size for the volume %s, try with automigrate set to true in order to check in the other suitable pools for the new size and then migrate & resize volume there.", volume.getUuid(), volume.getName()));

Check warning on line 1295 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1295

Added line #L1295 was not covered by tests
}
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOfferingId, currentSize, newMinIops, newMaxIops, true, false);
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resize failed for volume ID: %s as no suitable pool(s) found for migrating to support new disk offering or new size", volume.getUuid()));

Check warning on line 1300 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1300

Added line #L1300 was not covered by tests
}
final Long newSizeFinal = newSize;
suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resize failed for volume ID: %s as no suitable pool(s) with enough space found.", volume.getUuid()));

Check warning on line 1305 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1305

Added line #L1305 was not covered by tests
}
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
volumeMigrateRequired = true;
}

boolean volumeResizeRequired = false;
if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) {
volumeResizeRequired = true;
}
if (!volumeMigrateRequired && !volumeResizeRequired && newDiskOffering != null) {
_volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId());
volume = _volsDao.findById(volume.getId());
updateStorageWithTheNewDiskOffering(volume, newDiskOffering);

Check warning on line 1318 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1316-L1318

Added lines #L1316 - L1318 were not covered by tests

return volume;

Check warning on line 1320 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1320

Added line #L1320 was not covered by tests
}

if (volumeMigrateRequired) {
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOfferingId, true);
try {
Volume result = migrateVolume(migrateVolumeCmd);
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
if (volume == null) {
throw new CloudRuntimeException(String.format("Volume resize operation failed for volume ID: %s as migration failed to storage pool %s accommodating new size", volume.getUuid(), suitableStoragePoolsWithEnoughSpace.get(0).getId()));

Check warning on line 1329 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1329

Added line #L1329 was not covered by tests
}
} catch (Exception e) {
throw new CloudRuntimeException(String.format("Volume resize operation failed for volume ID: %s as migration failed to storage pool %s accommodating new size", volume.getUuid(), suitableStoragePoolsWithEnoughSpace.get(0).getId()));

Check warning on line 1332 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L1331-L1332

Added lines #L1331 - L1332 were not covered by tests
}
}

UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());

if (userVm != null) {
Expand Down Expand Up @@ -1973,6 +2018,7 @@
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();
Expand Down Expand Up @@ -2055,7 +2101,7 @@

StoragePoolVO existingStoragePool = _storagePoolDao.findById(volume.getPoolId());

Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), newSize, newMinIops, newMaxIops, true, false);
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), currentSize, newMinIops, newMaxIops, true, false);

Check warning on line 2104 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L2104

Added line #L2104 was not covered by tests
List<? extends StoragePool> suitableStoragePools = poolsPair.second();

if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) {
Expand All @@ -2077,10 +2123,16 @@
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);
final Long newSizeFinal = newSize;
List<? extends StoragePool> suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList());

Check warning on line 2127 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L2126-L2127

Added lines #L2126 - L2127 were not covered by tests
if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume change offering operation failed for volume ID: %s as no suitable pool(s) with enough space found for volume migration.", volume.getUuid()));

Check warning on line 2129 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L2129

Added line #L2129 was not covered by tests
}
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true);

Check warning on line 2132 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L2131-L2132

Added lines #L2131 - L2132 were not covered by tests
try {
volume = (VolumeVO) migrateVolume(migrateVolumeCmd);
Volume result = migrateVolume(migrateVolumeCmd);

Check warning on line 2134 in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java#L2134

Added line #L2134 was not covered by tests
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
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()));
}
Expand Down
Loading
Loading