Skip to content

Commit 26a34e2

Browse files
weizhouapachesureshanaparti
authored andcommitted
Resize volume: add pool capacity disablethreshold for resize and allow volume auto migration (apache#9761)
* server: add global settings for volume resize * resizeVolume: support automigrate * Address Suresh's comments * Update api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java Co-authored-by: Suresh Kumar Anaparti <[email protected]> * address Suresh's comments * UI: add autoMigrate to resizeVolume * resizevolume: add unit tests * resizevolume: add unit test for Allocated volume --------- Co-authored-by: Suresh Kumar Anaparti <[email protected]>
1 parent 3a45bd7 commit 26a34e2

File tree

10 files changed

+309
-13
lines changed

10 files changed

+309
-13
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ public class ResizeVolumeCmd extends BaseAsyncCmd implements UserCmd {
7373
description = "new disk offering id")
7474
private Long newDiskOfferingId;
7575

76+
@Parameter(name = ApiConstants.AUTO_MIGRATE, type = CommandType.BOOLEAN, required = false,
77+
description = "Flag to allow automatic migration of the volume to another suitable storage pool that accommodates the new size", since = "4.20.1")
78+
private Boolean autoMigrate;
79+
7680
/////////////////////////////////////////////////////
7781
/////////////////// Accessors ///////////////////////
7882
/////////////////////////////////////////////////////
@@ -129,6 +133,10 @@ public Long getNewDiskOfferingId() {
129133
return newDiskOfferingId;
130134
}
131135

136+
public boolean getAutoMigrate() {
137+
return autoMigrate == null ? false : autoMigrate;
138+
}
139+
132140
/////////////////////////////////////////////////////
133141
/////////////// API Implementation///////////////////
134142
/////////////////////////////////////////////////////

engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public interface CapacityManager {
4040
static final String StorageCapacityDisableThresholdCK = "pool.storage.capacity.disablethreshold";
4141
static final String StorageOverprovisioningFactorCK = "storage.overprovisioning.factor";
4242
static final String StorageAllocatedCapacityDisableThresholdCK = "pool.storage.allocated.capacity.disablethreshold";
43+
static final String StorageAllocatedCapacityDisableThresholdForVolumeResizeCK = "pool.storage.allocated.resize.capacity.disablethreshold";
4344

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

122+
static final ConfigKey<Double> StorageAllocatedCapacityDisableThresholdForVolumeSize =
123+
new ConfigKey<>(
124+
ConfigKey.CATEGORY_ALERT,
125+
Double.class,
126+
StorageAllocatedCapacityDisableThresholdForVolumeResizeCK,
127+
"0.90",
128+
"Percentage (as a value between 0 and 1) of allocated storage utilization above which allocators will disable using the pool for volume resize. " +
129+
"This is applicable only when volume.resize.allowed.beyond.allocation is set to true.",
130+
true,
131+
ConfigKey.Scope.Zone);
132+
121133
public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId);
122134

123135
void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost);

engine/components-api/src/main/java/com/cloud/storage/StorageManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ public interface StorageManager extends StorageService {
209209
ConfigKey<Long> HEURISTICS_SCRIPT_TIMEOUT = new ConfigKey<>("Advanced", Long.class, "heuristics.script.timeout", "3000",
210210
"The maximum runtime, in milliseconds, to execute the heuristic rule; if it is reached, a timeout will happen.", true);
211211

212+
ConfigKey<Boolean> AllowVolumeReSizeBeyondAllocation = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false",
213+
"Determines whether volume size can exceed the pool capacity allocation disable threshold (pool.storage.allocated.capacity.disablethreshold) " +
214+
"when resize a volume upto resize capacity disable threshold (pool.storage.allocated.resize.capacity.disablethreshold)",
215+
true, ConfigKey.Scope.Zone);
216+
212217
/**
213218
* should we execute in sequence not involving any storages?
214219
* @return tru if commands should execute in sequence

server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,7 @@ public String getConfigComponentName() {
12541254
@Override
12551255
public ConfigKey<?>[] getConfigKeys() {
12561256
return new ConfigKey<?>[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor,
1257-
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold};
1257+
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold,
1258+
StorageAllocatedCapacityDisableThresholdForVolumeSize };
12581259
}
12591260
}

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ protected void weightBasedParametersForValidation() {
598598
weightBasedParametersForValidation.add(Config.ManagementServerDatabaseStorageCapacityThreshold.key());
599599
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThreshold.key());
600600
weightBasedParametersForValidation.add(CapacityManager.StorageCapacityDisableThreshold.key());
601+
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key());
601602
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterCPUCapacityDisableThreshold.key());
602603
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterMemoryCapacityDisableThreshold.key());
603604
weightBasedParametersForValidation.add(Config.AgentLoadThreshold.key());

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3109,7 +3109,7 @@ public boolean storagePoolHasEnoughSpaceForResize(StoragePool pool, long current
31093109
} else {
31103110
final StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
31113111
final long allocatedSizeWithTemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null);
3112-
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize);
3112+
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);
31133113
}
31143114
}
31153115

@@ -3172,6 +3172,10 @@ public boolean isStoragePoolCompliantWithStoragePolicy(List<Pair<Volume, DiskPro
31723172
}
31733173

31743174
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize) {
3175+
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, false);
3176+
}
3177+
3178+
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize, boolean forVolumeResize) {
31753179
// allocated space includes templates
31763180
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
31773181

@@ -3204,10 +3208,22 @@ protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemp
32043208
if (usedPercentage > storageAllocatedThreshold) {
32053209
if (logger.isDebugEnabled()) {
32063210
logger.debug("Insufficient un-allocated capacity on: " + pool.getId() + " for storage allocation since its allocated percentage: " + usedPercentage
3207-
+ " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + storageAllocatedThreshold + ", skipping this pool");
3211+
+ " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + storageAllocatedThreshold);
3212+
}
3213+
if (!forVolumeResize) {
3214+
return false;
3215+
}
3216+
if (!AllowVolumeReSizeBeyondAllocation.valueIn(pool.getDataCenterId())) {
3217+
logger.debug(String.format("Skipping the pool %s as %s is false", pool, AllowVolumeReSizeBeyondAllocation.key()));
3218+
return false;
32083219
}
32093220

3210-
return false;
3221+
double storageAllocatedThresholdForResize = CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.valueIn(pool.getDataCenterId());
3222+
if (usedPercentage > storageAllocatedThresholdForResize) {
3223+
logger.debug(String.format("Skipping the pool %s since its allocated percentage: %s has crossed the allocated %s: %s",
3224+
pool, usedPercentage, CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key(), storageAllocatedThresholdForResize));
3225+
return false;
3226+
}
32113227
}
32123228

32133229
if (totalOverProvCapacity < (allocatedSizeWithTemplate + totalAskingSize)) {
@@ -4058,7 +4074,8 @@ public ConfigKey<?>[] getConfigKeys() {
40584074
MountDisabledStoragePool,
40594075
VmwareCreateCloneFull,
40604076
VmwareAllowParallelExecution,
4061-
DataStoreDownloadFollowRedirects
4077+
DataStoreDownloadFollowRedirects,
4078+
AllowVolumeReSizeBeyondAllocation
40624079
};
40634080
}
40644081

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

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,7 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
11131113
Long newMaxIops = cmd.getMaxIops();
11141114
Integer newHypervisorSnapshotReserve = null;
11151115
boolean shrinkOk = cmd.isShrinkOk();
1116+
boolean autoMigrateVolume = cmd.getAutoMigrate();
11161117

11171118
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
11181119
if (volume == null) {
@@ -1174,8 +1175,6 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
11741175
newSize = volume.getSize();
11751176
}
11761177

1177-
newMinIops = cmd.getMinIops();
1178-
11791178
if (newMinIops != null) {
11801179
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
11811180
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter.");
@@ -1185,8 +1184,6 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
11851184
newMinIops = volume.getMinIops();
11861185
}
11871186

1188-
newMaxIops = cmd.getMaxIops();
1189-
11901187
if (newMaxIops != null) {
11911188
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
11921189
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter.");
@@ -1308,6 +1305,54 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
13081305
return volume;
13091306
}
13101307

1308+
Long newDiskOfferingId = newDiskOffering != null ? newDiskOffering.getId() : diskOffering.getId();
1309+
1310+
boolean volumeMigrateRequired = false;
1311+
List<? extends StoragePool> suitableStoragePoolsWithEnoughSpace = null;
1312+
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
1313+
if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) {
1314+
if (!autoMigrateVolume) {
1315+
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()));
1316+
}
1317+
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOfferingId, currentSize, newMinIops, newMaxIops, true, false);
1318+
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
1319+
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
1320+
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()));
1321+
}
1322+
final Long newSizeFinal = newSize;
1323+
suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList());
1324+
if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) {
1325+
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()));
1326+
}
1327+
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
1328+
volumeMigrateRequired = true;
1329+
}
1330+
1331+
boolean volumeResizeRequired = false;
1332+
if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) {
1333+
volumeResizeRequired = true;
1334+
}
1335+
if (!volumeMigrateRequired && !volumeResizeRequired && newDiskOffering != null) {
1336+
_volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId());
1337+
volume = _volsDao.findById(volume.getId());
1338+
updateStorageWithTheNewDiskOffering(volume, newDiskOffering);
1339+
1340+
return volume;
1341+
}
1342+
1343+
if (volumeMigrateRequired) {
1344+
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOfferingId, true);
1345+
try {
1346+
Volume result = migrateVolume(migrateVolumeCmd);
1347+
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
1348+
if (volume == null) {
1349+
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()));
1350+
}
1351+
} catch (Exception e) {
1352+
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()));
1353+
}
1354+
}
1355+
13111356
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
13121357

13131358
if (userVm != null) {
@@ -2000,6 +2045,7 @@ public Outcome<Pair> checkAndRepairVolumeThroughJobQueue(final Long vmId, final
20002045
public Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException {
20012046
Long newSize = cmd.getSize();
20022047
Long newMinIops = cmd.getMinIops();
2048+
20032049
Long newMaxIops = cmd.getMaxIops();
20042050
Long newDiskOfferingId = cmd.getNewDiskOfferingId();
20052051
boolean shrinkOk = cmd.isShrinkOk();
@@ -2082,7 +2128,7 @@ public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOff
20822128

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

2085-
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), newSize, newMinIops, newMaxIops, true, false);
2131+
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), currentSize, newMinIops, newMaxIops, true, false);
20862132
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
20872133

20882134
if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) {
@@ -2104,10 +2150,16 @@ public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOff
21042150
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
21052151
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()));
21062152
}
2107-
Collections.shuffle(suitableStoragePools);
2108-
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePools.get(0).getId(), newDiskOffering.getId(), true);
2153+
final Long newSizeFinal = newSize;
2154+
List<? extends StoragePool> suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList());
2155+
if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) {
2156+
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()));
2157+
}
2158+
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
2159+
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true);
21092160
try {
2110-
volume = (VolumeVO) migrateVolume(migrateVolumeCmd);
2161+
Volume result = migrateVolume(migrateVolumeCmd);
2162+
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
21112163
if (volume == null) {
21122164
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()));
21132165
}

0 commit comments

Comments
 (0)