Skip to content

Commit 8a1da38

Browse files
Resize volume: add pool capacity disablethreshold for resize and allow volume auto migration (#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 34056d9 commit 8a1da38

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
@@ -591,6 +591,7 @@ protected void weightBasedParametersForValidation() {
591591
weightBasedParametersForValidation.add(Config.LocalStorageCapacityThreshold.key());
592592
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThreshold.key());
593593
weightBasedParametersForValidation.add(CapacityManager.StorageCapacityDisableThreshold.key());
594+
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key());
594595
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterCPUCapacityDisableThreshold.key());
595596
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterMemoryCapacityDisableThreshold.key());
596597
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
@@ -3101,7 +3101,7 @@ public boolean storagePoolHasEnoughSpaceForResize(StoragePool pool, long current
31013101
} else {
31023102
final StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
31033103
final long allocatedSizeWithTemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null);
3104-
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize);
3104+
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);
31053105
}
31063106
}
31073107

@@ -3164,6 +3164,10 @@ public boolean isStoragePoolCompliantWithStoragePolicy(List<Pair<Volume, DiskPro
31643164
}
31653165

31663166
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize) {
3167+
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, false);
3168+
}
3169+
3170+
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize, boolean forVolumeResize) {
31673171
// allocated space includes templates
31683172
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
31693173

@@ -3196,10 +3200,22 @@ protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemp
31963200
if (usedPercentage > storageAllocatedThreshold) {
31973201
if (logger.isDebugEnabled()) {
31983202
logger.debug("Insufficient un-allocated capacity on: " + pool.getId() + " for storage allocation since its allocated percentage: " + usedPercentage
3199-
+ " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + storageAllocatedThreshold + ", skipping this pool");
3203+
+ " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + storageAllocatedThreshold);
3204+
}
3205+
if (!forVolumeResize) {
3206+
return false;
3207+
}
3208+
if (!AllowVolumeReSizeBeyondAllocation.valueIn(pool.getDataCenterId())) {
3209+
logger.debug(String.format("Skipping the pool %s as %s is false", pool, AllowVolumeReSizeBeyondAllocation.key()));
3210+
return false;
32003211
}
32013212

3202-
return false;
3213+
double storageAllocatedThresholdForResize = CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.valueIn(pool.getDataCenterId());
3214+
if (usedPercentage > storageAllocatedThresholdForResize) {
3215+
logger.debug(String.format("Skipping the pool %s since its allocated percentage: %s has crossed the allocated %s: %s",
3216+
pool, usedPercentage, CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key(), storageAllocatedThresholdForResize));
3217+
return false;
3218+
}
32033219
}
32043220

32053221
if (totalOverProvCapacity < (allocatedSizeWithTemplate + totalAskingSize)) {
@@ -4050,7 +4066,8 @@ public ConfigKey<?>[] getConfigKeys() {
40504066
MountDisabledStoragePool,
40514067
VmwareCreateCloneFull,
40524068
VmwareAllowParallelExecution,
4053-
DataStoreDownloadFollowRedirects
4069+
DataStoreDownloadFollowRedirects,
4070+
AllowVolumeReSizeBeyondAllocation
40544071
};
40554072
}
40564073

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

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
10931093
Long newMaxIops = cmd.getMaxIops();
10941094
Integer newHypervisorSnapshotReserve = null;
10951095
boolean shrinkOk = cmd.isShrinkOk();
1096+
boolean autoMigrateVolume = cmd.getAutoMigrate();
10961097

10971098
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
10981099
if (volume == null) {
@@ -1154,8 +1155,6 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
11541155
newSize = volume.getSize();
11551156
}
11561157

1157-
newMinIops = cmd.getMinIops();
1158-
11591158
if (newMinIops != null) {
11601159
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
11611160
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter.");
@@ -1165,8 +1164,6 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
11651164
newMinIops = volume.getMinIops();
11661165
}
11671166

1168-
newMaxIops = cmd.getMaxIops();
1169-
11701167
if (newMaxIops != null) {
11711168
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
11721169
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter.");
@@ -1288,6 +1285,54 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
12881285
return volume;
12891286
}
12901287

1288+
Long newDiskOfferingId = newDiskOffering != null ? newDiskOffering.getId() : diskOffering.getId();
1289+
1290+
boolean volumeMigrateRequired = false;
1291+
List<? extends StoragePool> suitableStoragePoolsWithEnoughSpace = null;
1292+
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
1293+
if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) {
1294+
if (!autoMigrateVolume) {
1295+
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()));
1296+
}
1297+
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOfferingId, currentSize, newMinIops, newMaxIops, true, false);
1298+
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
1299+
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
1300+
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()));
1301+
}
1302+
final Long newSizeFinal = newSize;
1303+
suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList());
1304+
if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) {
1305+
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()));
1306+
}
1307+
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
1308+
volumeMigrateRequired = true;
1309+
}
1310+
1311+
boolean volumeResizeRequired = false;
1312+
if (currentSize != newSize || !compareEqualsIncludingNullOrZero(newMaxIops, volume.getMaxIops()) || !compareEqualsIncludingNullOrZero(newMinIops, volume.getMinIops())) {
1313+
volumeResizeRequired = true;
1314+
}
1315+
if (!volumeMigrateRequired && !volumeResizeRequired && newDiskOffering != null) {
1316+
_volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId());
1317+
volume = _volsDao.findById(volume.getId());
1318+
updateStorageWithTheNewDiskOffering(volume, newDiskOffering);
1319+
1320+
return volume;
1321+
}
1322+
1323+
if (volumeMigrateRequired) {
1324+
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOfferingId, true);
1325+
try {
1326+
Volume result = migrateVolume(migrateVolumeCmd);
1327+
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
1328+
if (volume == null) {
1329+
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()));
1330+
}
1331+
} catch (Exception e) {
1332+
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()));
1333+
}
1334+
}
1335+
12911336
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
12921337

12931338
if (userVm != null) {
@@ -1973,6 +2018,7 @@ public Outcome<Pair> checkAndRepairVolumeThroughJobQueue(final Long vmId, final
19732018
public Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException {
19742019
Long newSize = cmd.getSize();
19752020
Long newMinIops = cmd.getMinIops();
2021+
19762022
Long newMaxIops = cmd.getMaxIops();
19772023
Long newDiskOfferingId = cmd.getNewDiskOfferingId();
19782024
boolean shrinkOk = cmd.isShrinkOk();
@@ -2055,7 +2101,7 @@ public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOff
20552101

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

2058-
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), newSize, newMinIops, newMaxIops, true, false);
2104+
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForSystemMigrationOfVolume(volume.getId(), newDiskOffering.getId(), currentSize, newMinIops, newMaxIops, true, false);
20592105
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
20602106

20612107
if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) {
@@ -2077,10 +2123,16 @@ public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOff
20772123
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
20782124
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()));
20792125
}
2080-
Collections.shuffle(suitableStoragePools);
2081-
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePools.get(0).getId(), newDiskOffering.getId(), true);
2126+
final Long newSizeFinal = newSize;
2127+
List<? extends StoragePool> suitableStoragePoolsWithEnoughSpace = suitableStoragePools.stream().filter(pool -> storageMgr.storagePoolHasEnoughSpaceForResize(pool, 0L, newSizeFinal)).collect(Collectors.toList());
2128+
if (CollectionUtils.isEmpty(suitableStoragePoolsWithEnoughSpace)) {
2129+
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()));
2130+
}
2131+
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
2132+
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true);
20822133
try {
2083-
volume = (VolumeVO) migrateVolume(migrateVolumeCmd);
2134+
Volume result = migrateVolume(migrateVolumeCmd);
2135+
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
20842136
if (volume == null) {
20852137
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()));
20862138
}

0 commit comments

Comments
 (0)