Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1524,13 +1524,26 @@ protected boolean isNotPossibleToResize(VolumeVO volume, DiskOfferingVO diskOffe
return isOfferingEnforcingRootDiskSize && isRoot && isNotIso;
}

private void checkIfVolumeIsRootAndVmIsRunning(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
if (!volume.getSize().equals(newSize) && volume.getVolumeType().equals(Volume.Type.ROOT) && !State.Stopped.equals(vmInstanceVO.getState())) {
throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] when VM is not on Stopped State. VM %s is in state %s", volume.getName(), vmInstanceVO
protected void checkIfVolumeIsRootAndVmIsRunning(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
if (!volume.getSize().equals(newSize) && volume.getVolumeType().equals(Volume.Type.ROOT) && !State.Stopped.equals(vmInstanceVO.getState())
&& !isGrowOfVolumeOnRunningKvmVm(newSize, volume, vmInstanceVO)) {
throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] when VM is not on Stopped State. VM %s is in state %s. "
+ "Only growing the ROOT volume of a Running KVM user VM is allowed without stopping it.", volume.getName(), vmInstanceVO
.getInstanceName(), vmInstanceVO.getState()));
}
}

/**
* KVM grows the disk of a running user VM in place (virsh blockresize), and a size-only resizeVolume of a
* ROOT volume already does so. Growing it through a new disk offering is the same operation.
* Shrinking still requires the VM to be stopped. Only user VMs are let through, since the resize orchestration
* (orchestrateResizeVolume) only looks up the running VM and its host for user VMs.
*/
private boolean isGrowOfVolumeOnRunningKvmVm(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
return HypervisorType.KVM.equals(vmInstanceVO.getHypervisorType()) && VirtualMachine.Type.User.equals(vmInstanceVO.getType())
&& State.Running.equals(vmInstanceVO.getState()) && newSize != null && newSize > volume.getSize();
}

private void validateIops(Long minIops, Long maxIops, Storage.StoragePoolType poolType) {
if (poolType == Storage.StoragePoolType.PowerFlex) {
// PowerFlex takes iopsLimit as input, skip minIops validation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
import com.cloud.vm.DiskProfile;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineManager;
Expand Down Expand Up @@ -2504,4 +2505,59 @@ public void testResizeVolumeInternal_VMware_VMRunning_ShouldThrowStateGuardError
e.getMessage() != null && e.getMessage().contains("VM should be in"));
}
}

private VolumeVO mockRootVolumeOfSize(long size) {
VolumeVO volume = Mockito.mock(VolumeVO.class);
Mockito.when(volume.getSize()).thenReturn(size);
Mockito.when(volume.getVolumeType()).thenReturn(Volume.Type.ROOT);
return volume;
}

private VMInstanceVO mockVm(HypervisorType hypervisorType, State state) {
VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
Mockito.lenient().when(vm.getHypervisorType()).thenReturn(hypervisorType);
Mockito.lenient().when(vm.getType()).thenReturn(VirtualMachine.Type.User);
Mockito.when(vm.getState()).thenReturn(state);
return vm;
}

@Test
public void checkIfVolumeIsRootAndVmIsRunningAllowsGrowOnRunningKvmVm() {
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.KVM, State.Running));
}

@Test(expected = InvalidParameterValueException.class)
public void checkIfVolumeIsRootAndVmIsRunningRefusesShrinkOnRunningKvmVm() {
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(5L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.KVM, State.Running));
}

@Test(expected = InvalidParameterValueException.class)
public void checkIfVolumeIsRootAndVmIsRunningRefusesGrowOnRunningNonKvmVm() {
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.VMware, State.Running));
}

@Test(expected = InvalidParameterValueException.class)
public void checkIfVolumeIsRootAndVmIsRunningRefusesGrowOnRunningKvmSystemVm() {
VMInstanceVO vm = mockVm(HypervisorType.KVM, State.Running);
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), vm);
}

@Test(expected = InvalidParameterValueException.class)
public void checkIfVolumeIsRootAndVmIsRunningRefusesGrowOnKvmVmNotRunning() {
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.KVM, State.Migrating));
}

@Test
public void checkIfVolumeIsRootAndVmIsRunningAllowsResizeOnStoppedVm() {
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(5L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.VMware, State.Stopped));
}

@Test
public void checkIfVolumeIsRootAndVmIsRunningIgnoresDataDisk() {
VolumeVO volume = Mockito.mock(VolumeVO.class);
Mockito.when(volume.getSize()).thenReturn(10L << 30);
Mockito.when(volume.getVolumeType()).thenReturn(Volume.Type.DATADISK);
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(5L << 30, volume, Mockito.mock(VMInstanceVO.class));
}
}
Loading