mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
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 <suresh.anaparti@shapeblue.com> * 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 <suresh.anaparti@shapeblue.com>
This commit is contained in:
parent
34056d956c
commit
8a1da3804c
@ -73,6 +73,10 @@ public class ResizeVolumeCmd extends BaseAsyncCmd implements UserCmd {
|
|||||||
description = "new disk offering id")
|
description = "new disk offering id")
|
||||||
private Long newDiskOfferingId;
|
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 ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -129,6 +133,10 @@ public class ResizeVolumeCmd extends BaseAsyncCmd implements UserCmd {
|
|||||||
return newDiskOfferingId;
|
return newDiskOfferingId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getAutoMigrate() {
|
||||||
|
return autoMigrate == null ? false : autoMigrate;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
/////////////// API Implementation///////////////////
|
/////////////// API Implementation///////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -40,6 +40,7 @@ public interface CapacityManager {
|
|||||||
static final String StorageCapacityDisableThresholdCK = "pool.storage.capacity.disablethreshold";
|
static final String StorageCapacityDisableThresholdCK = "pool.storage.capacity.disablethreshold";
|
||||||
static final String StorageOverprovisioningFactorCK = "storage.overprovisioning.factor";
|
static final String StorageOverprovisioningFactorCK = "storage.overprovisioning.factor";
|
||||||
static final String StorageAllocatedCapacityDisableThresholdCK = "pool.storage.allocated.capacity.disablethreshold";
|
static final String StorageAllocatedCapacityDisableThresholdCK = "pool.storage.allocated.capacity.disablethreshold";
|
||||||
|
static final String StorageAllocatedCapacityDisableThresholdForVolumeResizeCK = "pool.storage.allocated.resize.capacity.disablethreshold";
|
||||||
|
|
||||||
static final ConfigKey<Float> CpuOverprovisioningFactor =
|
static final ConfigKey<Float> CpuOverprovisioningFactor =
|
||||||
new ConfigKey<>(
|
new ConfigKey<>(
|
||||||
@ -118,6 +119,17 @@ public interface CapacityManager {
|
|||||||
"Percentage (as a value between 0 and 1) of secondary storage capacity threshold.",
|
"Percentage (as a value between 0 and 1) of secondary storage capacity threshold.",
|
||||||
true);
|
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);
|
public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId);
|
||||||
|
|
||||||
void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost);
|
void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost);
|
||||||
|
|||||||
@ -209,6 +209,11 @@ public interface StorageManager extends StorageService {
|
|||||||
ConfigKey<Long> HEURISTICS_SCRIPT_TIMEOUT = new ConfigKey<>("Advanced", Long.class, "heuristics.script.timeout", "3000",
|
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);
|
"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?
|
* should we execute in sequence not involving any storages?
|
||||||
* @return tru if commands should execute in sequence
|
* @return tru if commands should execute in sequence
|
||||||
|
|||||||
@ -1254,6 +1254,7 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager,
|
|||||||
@Override
|
@Override
|
||||||
public ConfigKey<?>[] getConfigKeys() {
|
public ConfigKey<?>[] getConfigKeys() {
|
||||||
return new ConfigKey<?>[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor,
|
return new ConfigKey<?>[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor,
|
||||||
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold};
|
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold,
|
||||||
|
StorageAllocatedCapacityDisableThresholdForVolumeSize };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -591,6 +591,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
weightBasedParametersForValidation.add(Config.LocalStorageCapacityThreshold.key());
|
weightBasedParametersForValidation.add(Config.LocalStorageCapacityThreshold.key());
|
||||||
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThreshold.key());
|
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThreshold.key());
|
||||||
weightBasedParametersForValidation.add(CapacityManager.StorageCapacityDisableThreshold.key());
|
weightBasedParametersForValidation.add(CapacityManager.StorageCapacityDisableThreshold.key());
|
||||||
|
weightBasedParametersForValidation.add(CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.key());
|
||||||
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterCPUCapacityDisableThreshold.key());
|
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterCPUCapacityDisableThreshold.key());
|
||||||
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterMemoryCapacityDisableThreshold.key());
|
weightBasedParametersForValidation.add(DeploymentClusterPlanner.ClusterMemoryCapacityDisableThreshold.key());
|
||||||
weightBasedParametersForValidation.add(Config.AgentLoadThreshold.key());
|
weightBasedParametersForValidation.add(Config.AgentLoadThreshold.key());
|
||||||
|
|||||||
@ -3101,7 +3101,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
|||||||
} else {
|
} else {
|
||||||
final StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
|
final StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
|
||||||
final long allocatedSizeWithTemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null);
|
final long allocatedSizeWithTemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null);
|
||||||
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize);
|
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3164,6 +3164,10 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize) {
|
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize) {
|
||||||
|
return checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean checkPoolforSpace(StoragePool pool, long allocatedSizeWithTemplate, long totalAskingSize, boolean forVolumeResize) {
|
||||||
// allocated space includes templates
|
// allocated space includes templates
|
||||||
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
|
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
|
||||||
|
|
||||||
@ -3196,10 +3200,22 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
|||||||
if (usedPercentage > storageAllocatedThreshold) {
|
if (usedPercentage > storageAllocatedThreshold) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Insufficient un-allocated capacity on: " + pool.getId() + " for storage allocation since its allocated percentage: " + usedPercentage
|
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)) {
|
if (totalOverProvCapacity < (allocatedSizeWithTemplate + totalAskingSize)) {
|
||||||
@ -4050,7 +4066,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
|||||||
MountDisabledStoragePool,
|
MountDisabledStoragePool,
|
||||||
VmwareCreateCloneFull,
|
VmwareCreateCloneFull,
|
||||||
VmwareAllowParallelExecution,
|
VmwareAllowParallelExecution,
|
||||||
DataStoreDownloadFollowRedirects
|
DataStoreDownloadFollowRedirects,
|
||||||
|
AllowVolumeReSizeBeyondAllocation
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1093,6 +1093,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
Long newMaxIops = cmd.getMaxIops();
|
Long newMaxIops = cmd.getMaxIops();
|
||||||
Integer newHypervisorSnapshotReserve = null;
|
Integer newHypervisorSnapshotReserve = null;
|
||||||
boolean shrinkOk = cmd.isShrinkOk();
|
boolean shrinkOk = cmd.isShrinkOk();
|
||||||
|
boolean autoMigrateVolume = cmd.getAutoMigrate();
|
||||||
|
|
||||||
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
|
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
|
||||||
if (volume == null) {
|
if (volume == null) {
|
||||||
@ -1154,8 +1155,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
newSize = volume.getSize();
|
newSize = volume.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
newMinIops = cmd.getMinIops();
|
|
||||||
|
|
||||||
if (newMinIops != null) {
|
if (newMinIops != null) {
|
||||||
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
|
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.");
|
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter.");
|
||||||
@ -1165,8 +1164,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
newMinIops = volume.getMinIops();
|
newMinIops = volume.getMinIops();
|
||||||
}
|
}
|
||||||
|
|
||||||
newMaxIops = cmd.getMaxIops();
|
|
||||||
|
|
||||||
if (newMaxIops != null) {
|
if (newMaxIops != null) {
|
||||||
if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
|
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.");
|
throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter.");
|
||||||
@ -1288,6 +1285,54 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
return volume;
|
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()));
|
||||||
|
}
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
} 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
|
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
|
||||||
|
|
||||||
if (userVm != null) {
|
if (userVm != null) {
|
||||||
@ -1973,6 +2018,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
public Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException {
|
public Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException {
|
||||||
Long newSize = cmd.getSize();
|
Long newSize = cmd.getSize();
|
||||||
Long newMinIops = cmd.getMinIops();
|
Long newMinIops = cmd.getMinIops();
|
||||||
|
|
||||||
Long newMaxIops = cmd.getMaxIops();
|
Long newMaxIops = cmd.getMaxIops();
|
||||||
Long newDiskOfferingId = cmd.getNewDiskOfferingId();
|
Long newDiskOfferingId = cmd.getNewDiskOfferingId();
|
||||||
boolean shrinkOk = cmd.isShrinkOk();
|
boolean shrinkOk = cmd.isShrinkOk();
|
||||||
@ -2055,7 +2101,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
|
|
||||||
StoragePoolVO existingStoragePool = _storagePoolDao.findById(volume.getPoolId());
|
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);
|
||||||
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
|
List<? extends StoragePool> suitableStoragePools = poolsPair.second();
|
||||||
|
|
||||||
if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) {
|
if (!suitableStoragePools.stream().anyMatch(p -> (p.getId() == existingStoragePool.getId()))) {
|
||||||
@ -2077,10 +2123,16 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
|
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()));
|
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);
|
final Long newSizeFinal = newSize;
|
||||||
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePools.get(0).getId(), newDiskOffering.getId(), true);
|
List<? extends StoragePool> 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 change offering operation failed for volume ID: %s as no suitable pool(s) with enough space found for volume migration.", volume.getUuid()));
|
||||||
|
}
|
||||||
|
Collections.shuffle(suitableStoragePoolsWithEnoughSpace);
|
||||||
|
MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true);
|
||||||
try {
|
try {
|
||||||
volume = (VolumeVO) migrateVolume(migrateVolumeCmd);
|
Volume result = migrateVolume(migrateVolumeCmd);
|
||||||
|
volume = (result != null) ? _volsDao.findById(result.getId()) : null;
|
||||||
if (volume == 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()));
|
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()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package com.cloud.storage;
|
package com.cloud.storage;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -42,6 +43,7 @@ import com.cloud.vm.dao.VMInstanceDao;
|
|||||||
|
|
||||||
import org.apache.cloudstack.api.ApiConstants;
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
|
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
|
||||||
import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
|
import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
|
||||||
@ -756,4 +758,81 @@ public class StorageManagerImplTest {
|
|||||||
String failureReason = storageManagerImpl.getStoragePoolMountFailureReason(error);
|
String failureReason = storageManagerImpl.getStoragePoolMountFailureReason(error);
|
||||||
Assert.assertEquals(failureReason, "An incorrect mount option was specified");
|
Assert.assertEquals(failureReason, "An incorrect mount option was specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||||
|
Field f = ConfigKey.class.getDeclaredField(name);
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(configKey, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long testCheckPoolforSpaceForResizeSetup(StoragePoolVO pool, Long allocatedSizeWithTemplate) {
|
||||||
|
Long poolId = 10L;
|
||||||
|
Long zoneId = 2L;
|
||||||
|
|
||||||
|
Long capacityBytes = (long) (allocatedSizeWithTemplate / Double.valueOf(CapacityManager.StorageAllocatedCapacityDisableThreshold.defaultValue())
|
||||||
|
/ Double.valueOf(CapacityManager.StorageOverprovisioningFactor.defaultValue()));
|
||||||
|
Long maxAllocatedSizeForResize = (long) (capacityBytes * Double.valueOf(CapacityManager.StorageOverprovisioningFactor.defaultValue())
|
||||||
|
* Double.valueOf(CapacityManager.StorageAllocatedCapacityDisableThresholdForVolumeSize.defaultValue()));
|
||||||
|
|
||||||
|
System.out.println("maxAllocatedSizeForResize = " + maxAllocatedSizeForResize);
|
||||||
|
System.out.println("allocatedSizeWithTemplate = " + allocatedSizeWithTemplate);
|
||||||
|
|
||||||
|
Mockito.when(pool.getId()).thenReturn(poolId);
|
||||||
|
Mockito.when(pool.getCapacityBytes()).thenReturn(capacityBytes);
|
||||||
|
Mockito.when(pool.getDataCenterId()).thenReturn(zoneId);
|
||||||
|
Mockito.when(storagePoolDao.findById(poolId)).thenReturn(pool);
|
||||||
|
Mockito.when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
|
||||||
|
|
||||||
|
return maxAllocatedSizeForResize - allocatedSizeWithTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckPoolforSpaceForResize1() {
|
||||||
|
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Long allocatedSizeWithTemplate = 100L * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
Long maxAskingSize = testCheckPoolforSpaceForResizeSetup(pool, allocatedSizeWithTemplate);
|
||||||
|
Long totalAskingSize = maxAskingSize / 2;
|
||||||
|
|
||||||
|
boolean result = storageManagerImpl.checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, false);
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckPoolforSpaceForResize2() {
|
||||||
|
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Long allocatedSizeWithTemplate = 100L * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
Long maxAskingSize = testCheckPoolforSpaceForResizeSetup(pool, allocatedSizeWithTemplate);
|
||||||
|
Long totalAskingSize = maxAskingSize / 2;
|
||||||
|
|
||||||
|
boolean result = storageManagerImpl.checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckPoolforSpaceForResize3() throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Long allocatedSizeWithTemplate = 100L * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
Long maxAskingSize = testCheckPoolforSpaceForResizeSetup(pool, allocatedSizeWithTemplate);
|
||||||
|
Long totalAskingSize = maxAskingSize + 1;
|
||||||
|
overrideDefaultConfigValue(StorageManagerImpl.AllowVolumeReSizeBeyondAllocation, "_defaultValue", "true");
|
||||||
|
|
||||||
|
boolean result = storageManagerImpl.checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckPoolforSpaceForResize4() throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Long allocatedSizeWithTemplate = 100L * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
Long maxAskingSize = testCheckPoolforSpaceForResizeSetup(pool, allocatedSizeWithTemplate);
|
||||||
|
Long totalAskingSize = maxAskingSize / 2;
|
||||||
|
overrideDefaultConfigValue(StorageManagerImpl.AllowVolumeReSizeBeyondAllocation, "_defaultValue", "true");
|
||||||
|
|
||||||
|
boolean result = storageManagerImpl.checkPoolforSpace(pool, allocatedSizeWithTemplate, totalAskingSize, true);
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,10 +19,13 @@ package com.cloud.storage;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.nullable;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@ -38,14 +41,17 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import com.cloud.server.ManagementService;
|
||||||
import org.apache.cloudstack.acl.ControlledEntity;
|
import org.apache.cloudstack.acl.ControlledEntity;
|
||||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||||
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
||||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||||
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
|
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
|
||||||
import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
|
import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
|
||||||
import org.apache.cloudstack.backup.dao.BackupDao;
|
import org.apache.cloudstack.backup.dao.BackupDao;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
import org.apache.cloudstack.context.CallContext;
|
||||||
|
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
|
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
|
||||||
@ -85,6 +91,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
|
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
|
||||||
|
import com.cloud.configuration.ConfigurationManager;
|
||||||
import com.cloud.configuration.Resource;
|
import com.cloud.configuration.Resource;
|
||||||
import com.cloud.configuration.Resource.ResourceType;
|
import com.cloud.configuration.Resource.ResourceType;
|
||||||
import com.cloud.dc.DataCenterVO;
|
import com.cloud.dc.DataCenterVO;
|
||||||
@ -210,6 +217,8 @@ public class VolumeApiServiceImplTest {
|
|||||||
private StoragePool storagePoolMock;
|
private StoragePool storagePoolMock;
|
||||||
private long storagePoolMockId = 1;
|
private long storagePoolMockId = 1;
|
||||||
@Mock
|
@Mock
|
||||||
|
private DiskOfferingVO diskOfferingMock;
|
||||||
|
@Mock
|
||||||
private DiskOfferingVO newDiskOfferingMock;
|
private DiskOfferingVO newDiskOfferingMock;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@ -238,10 +247,20 @@ public class VolumeApiServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private StorageManager storageMgr;
|
private StorageManager storageMgr;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ConfigurationManager _configMgr;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private VolumeOrchestrationService _volumeMgr;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ManagementService managementService;
|
||||||
|
|
||||||
private long accountMockId = 456l;
|
private long accountMockId = 456l;
|
||||||
private long volumeMockId = 12313l;
|
private long volumeMockId = 12313l;
|
||||||
private long vmInstanceMockId = 1123l;
|
private long vmInstanceMockId = 1123l;
|
||||||
private long volumeSizeMock = 456789921939l;
|
private long volumeSizeMock = 456789921939l;
|
||||||
|
private long newVolumeSizeMock = 456789930000l;
|
||||||
private static long imageStoreId = 10L;
|
private static long imageStoreId = 10L;
|
||||||
|
|
||||||
private String projectMockUuid = "projectUuid";
|
private String projectMockUuid = "projectUuid";
|
||||||
@ -250,6 +269,7 @@ public class VolumeApiServiceImplTest {
|
|||||||
private long projectMockAccountId = 132329390L;
|
private long projectMockAccountId = 132329390L;
|
||||||
|
|
||||||
private long diskOfferingMockId = 100203L;
|
private long diskOfferingMockId = 100203L;
|
||||||
|
private long newDiskOfferingMockId = 100204L;
|
||||||
|
|
||||||
private long offeringMockId = 31902L;
|
private long offeringMockId = 31902L;
|
||||||
|
|
||||||
@ -1820,4 +1840,92 @@ public class VolumeApiServiceImplTest {
|
|||||||
|
|
||||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void testResizeVolumeSetup() throws ExecutionException, InterruptedException {
|
||||||
|
Long poolId = 11L;
|
||||||
|
|
||||||
|
when(volumeDaoMock.findById(volumeMockId)).thenReturn(volumeVoMock);
|
||||||
|
when(volumeVoMock.getId()).thenReturn(volumeMockId);
|
||||||
|
when(volumeDaoMock.getHypervisorType(volumeMockId)).thenReturn(HypervisorType.KVM);
|
||||||
|
when(volumeVoMock.getState()).thenReturn(Volume.State.Ready);
|
||||||
|
when(volumeVoMock.getDiskOfferingId()).thenReturn(diskOfferingMockId);
|
||||||
|
when(_diskOfferingDao.findById(diskOfferingMockId)).thenReturn(diskOfferingMock);
|
||||||
|
when(_diskOfferingDao.findById(newDiskOfferingMockId)).thenReturn(newDiskOfferingMock);
|
||||||
|
when(newDiskOfferingMock.getRemoved()).thenReturn(null);
|
||||||
|
when(diskOfferingMock.getDiskSizeStrictness()).thenReturn(false);
|
||||||
|
when(newDiskOfferingMock.getDiskSizeStrictness()).thenReturn(false);
|
||||||
|
when(volumeVoMock.getInstanceId()).thenReturn(null);
|
||||||
|
when(volumeVoMock.getVolumeType()).thenReturn(Type.DATADISK);
|
||||||
|
when(newDiskOfferingMock.getDiskSize()).thenReturn(newVolumeSizeMock);
|
||||||
|
|
||||||
|
VolumeInfo volInfo = Mockito.mock(VolumeInfo.class);
|
||||||
|
when(volumeDataFactoryMock.getVolume(volumeMockId)).thenReturn(volInfo);
|
||||||
|
DataStore dataStore = Mockito.mock(DataStore.class);
|
||||||
|
when((volInfo.getDataStore())).thenReturn(dataStore);
|
||||||
|
|
||||||
|
when(volumeVoMock.getPoolId()).thenReturn(poolId);
|
||||||
|
StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
when(primaryDataStoreDaoMock.findById(poolId)).thenReturn(storagePool);
|
||||||
|
|
||||||
|
Mockito.lenient().doReturn(asyncCallFutureVolumeapiResultMock).when(volumeServiceMock).resize(any(VolumeInfo.class));
|
||||||
|
Mockito.doReturn(Mockito.mock(VolumeApiResult.class)).when(asyncCallFutureVolumeapiResultMock).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResizeVolumeWithEnoughCapacity() throws ResourceAllocationException, ExecutionException, InterruptedException {
|
||||||
|
ResizeVolumeCmd cmd = new ResizeVolumeCmd();
|
||||||
|
ReflectionTestUtils.setField(cmd, "id", volumeMockId);
|
||||||
|
ReflectionTestUtils.setField(cmd, "newDiskOfferingId", newDiskOfferingMockId);
|
||||||
|
|
||||||
|
testResizeVolumeSetup();
|
||||||
|
|
||||||
|
when(storageMgr.storagePoolHasEnoughSpaceForResize(any(), nullable(Long.class), nullable(Long.class))).thenReturn(true);
|
||||||
|
|
||||||
|
try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
|
||||||
|
volumeApiServiceImpl.resizeVolume(cmd);
|
||||||
|
|
||||||
|
verify(volumeServiceMock).resize(any(VolumeInfo.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResizeVolumeWithoutEnoughCapacity() throws ResourceAllocationException, ExecutionException, InterruptedException {
|
||||||
|
ResizeVolumeCmd cmd = new ResizeVolumeCmd();
|
||||||
|
ReflectionTestUtils.setField(cmd, "id", volumeMockId);
|
||||||
|
ReflectionTestUtils.setField(cmd, "newDiskOfferingId", newDiskOfferingMockId);
|
||||||
|
ReflectionTestUtils.setField(cmd, "autoMigrate", true);
|
||||||
|
|
||||||
|
testResizeVolumeSetup();
|
||||||
|
|
||||||
|
when(storageMgr.storagePoolHasEnoughSpaceForResize(any(), nullable(Long.class), nullable(Long.class))).thenReturn(false).thenReturn(true);
|
||||||
|
StoragePoolVO suitableStoragePool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = new Pair<>(Arrays.asList(suitableStoragePool), Arrays.asList(suitableStoragePool));
|
||||||
|
when(managementService.listStoragePoolsForSystemMigrationOfVolume(anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyBoolean(), anyBoolean())).thenReturn(poolsPair);
|
||||||
|
doReturn(volumeInfoMock).when(volumeApiServiceImpl).migrateVolume(any());
|
||||||
|
when(volumeInfoMock.getId()).thenReturn(volumeMockId);
|
||||||
|
|
||||||
|
try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
|
||||||
|
volumeApiServiceImpl.resizeVolume(cmd);
|
||||||
|
|
||||||
|
verify(volumeApiServiceImpl).migrateVolume(any());
|
||||||
|
verify(volumeServiceMock).resize(any(VolumeInfo.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResizeVolumeInAllocateState() throws ResourceAllocationException, ExecutionException, InterruptedException {
|
||||||
|
ResizeVolumeCmd cmd = new ResizeVolumeCmd();
|
||||||
|
ReflectionTestUtils.setField(cmd, "id", volumeMockId);
|
||||||
|
ReflectionTestUtils.setField(cmd, "newDiskOfferingId", newDiskOfferingMockId);
|
||||||
|
|
||||||
|
testResizeVolumeSetup();
|
||||||
|
|
||||||
|
when(volumeVoMock.getState()).thenReturn(Volume.State.Allocated);
|
||||||
|
|
||||||
|
try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
|
||||||
|
volumeApiServiceImpl.resizeVolume(cmd);
|
||||||
|
|
||||||
|
verify(volumeServiceMock, times(0)).resize(any(VolumeInfo.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,15 @@
|
|||||||
:checked="shrinkOk"
|
:checked="shrinkOk"
|
||||||
@change="val => { shrinkOk = val }"/>
|
@change="val => { shrinkOk = val }"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item name="autoMigrate" ref="autoMigrate" :label="$t('label.automigrate.volume')">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.automigrate.volume')" :tooltip="apiParams.automigrate.description"/>
|
||||||
|
</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="form.autoMigrate"
|
||||||
|
:checked="autoMigrate"
|
||||||
|
@change="val => { autoMigrate = val }"/>
|
||||||
|
</a-form-item>
|
||||||
<div :span="24" class="action-button">
|
<div :span="24" class="action-button">
|
||||||
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
|
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
|
||||||
<a-button :loading="loading" type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
<a-button :loading="loading" type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
||||||
@ -58,9 +67,13 @@
|
|||||||
import { ref, reactive, toRaw } from 'vue'
|
import { ref, reactive, toRaw } from 'vue'
|
||||||
import { api } from '@/api'
|
import { api } from '@/api'
|
||||||
import { mixinForm } from '@/utils/mixin'
|
import { mixinForm } from '@/utils/mixin'
|
||||||
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ResizeVolume',
|
name: 'ResizeVolume',
|
||||||
|
components: {
|
||||||
|
TooltipLabel
|
||||||
|
},
|
||||||
mixins: [mixinForm],
|
mixins: [mixinForm],
|
||||||
props: {
|
props: {
|
||||||
resource: {
|
resource: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user