Skip to content

Commit c2202de

Browse files
Allow growing a running KVM VM's ROOT volume through a new disk offering
A size-only resizeVolume of a ROOT volume already works on a running KVM VM: orchestrateResizeVolume checks that the host is Up and Enabled, and the agent grows the disk in place with virsh blockresize. Growing the same volume by giving it a larger disk offering is refused, through resizeVolume with diskofferingid, through changeOfferingForVolume, and so through scaleVirtualMachine when allow.diskoffering.change.during.scale.vm is on: Cannot resize ROOT volume [...] when VM is not on Stopped State checkIfVolumeIsRootAndVmIsRunning, which the size-only path does not call, refuses any size change of a ROOT volume unless the VM is Stopped, on every hypervisor. Where the ROOT volume's offering fixes its size, the size-only path is refused too, so stopping the VM was the only way to grow its root disk. Let the check through when the volume belongs to a Running KVM user VM and the new size is larger than the current one. Shrinking, other hypervisors, system VMs and other VM states are refused as before. The resize then follows the same path as a size-only resize of a running VM. Signed-off-by: Brad House <bhouse@nexthop.ai>
1 parent d02adcd commit c2202de

2 files changed

Lines changed: 72 additions & 3 deletions

File tree

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,13 +1524,26 @@ protected boolean isNotPossibleToResize(VolumeVO volume, DiskOfferingVO diskOffe
15241524
return isOfferingEnforcingRootDiskSize && isRoot && isNotIso;
15251525
}
15261526

1527-
private void checkIfVolumeIsRootAndVmIsRunning(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
1528-
if (!volume.getSize().equals(newSize) && volume.getVolumeType().equals(Volume.Type.ROOT) && !State.Stopped.equals(vmInstanceVO.getState())) {
1529-
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
1527+
protected void checkIfVolumeIsRootAndVmIsRunning(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
1528+
if (!volume.getSize().equals(newSize) && volume.getVolumeType().equals(Volume.Type.ROOT) && !State.Stopped.equals(vmInstanceVO.getState())
1529+
&& !isGrowOfVolumeOnRunningKvmVm(newSize, volume, vmInstanceVO)) {
1530+
throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] when VM is not on Stopped State. VM %s is in state %s. "
1531+
+ "Only growing the ROOT volume of a Running KVM user VM is allowed without stopping it.", volume.getName(), vmInstanceVO
15301532
.getInstanceName(), vmInstanceVO.getState()));
15311533
}
15321534
}
15331535

1536+
/**
1537+
* KVM grows the disk of a running user VM in place (virsh blockresize), and a size-only resizeVolume of a
1538+
* ROOT volume already does so. Growing it through a new disk offering is the same operation.
1539+
* Shrinking still requires the VM to be stopped. Only user VMs are let through, since the resize orchestration
1540+
* (orchestrateResizeVolume) only looks up the running VM and its host for user VMs.
1541+
*/
1542+
private boolean isGrowOfVolumeOnRunningKvmVm(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
1543+
return HypervisorType.KVM.equals(vmInstanceVO.getHypervisorType()) && VirtualMachine.Type.User.equals(vmInstanceVO.getType())
1544+
&& State.Running.equals(vmInstanceVO.getState()) && newSize != null && newSize > volume.getSize();
1545+
}
1546+
15341547
private void validateIops(Long minIops, Long maxIops, Storage.StoragePoolType poolType) {
15351548
if (poolType == Storage.StoragePoolType.PowerFlex) {
15361549
// PowerFlex takes iopsLimit as input, skip minIops validation

‎server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
import com.cloud.vm.DiskProfile;
140140
import com.cloud.vm.UserVmManager;
141141
import com.cloud.vm.UserVmVO;
142+
import com.cloud.vm.VMInstanceVO;
142143
import com.cloud.vm.VirtualMachine;
143144
import com.cloud.vm.VirtualMachine.State;
144145
import com.cloud.vm.VirtualMachineManager;
@@ -2504,4 +2505,59 @@ public void testResizeVolumeInternal_VMware_VMRunning_ShouldThrowStateGuardError
25042505
e.getMessage() != null && e.getMessage().contains("VM should be in"));
25052506
}
25062507
}
2508+
2509+
private VolumeVO mockRootVolumeOfSize(long size) {
2510+
VolumeVO volume = Mockito.mock(VolumeVO.class);
2511+
Mockito.when(volume.getSize()).thenReturn(size);
2512+
Mockito.when(volume.getVolumeType()).thenReturn(Volume.Type.ROOT);
2513+
return volume;
2514+
}
2515+
2516+
private VMInstanceVO mockVm(HypervisorType hypervisorType, State state) {
2517+
VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
2518+
Mockito.lenient().when(vm.getHypervisorType()).thenReturn(hypervisorType);
2519+
Mockito.lenient().when(vm.getType()).thenReturn(VirtualMachine.Type.User);
2520+
Mockito.when(vm.getState()).thenReturn(state);
2521+
return vm;
2522+
}
2523+
2524+
@Test
2525+
public void checkIfVolumeIsRootAndVmIsRunningAllowsGrowOnRunningKvmVm() {
2526+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.KVM, State.Running));
2527+
}
2528+
2529+
@Test(expected = InvalidParameterValueException.class)
2530+
public void checkIfVolumeIsRootAndVmIsRunningRefusesShrinkOnRunningKvmVm() {
2531+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(5L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.KVM, State.Running));
2532+
}
2533+
2534+
@Test(expected = InvalidParameterValueException.class)
2535+
public void checkIfVolumeIsRootAndVmIsRunningRefusesGrowOnRunningNonKvmVm() {
2536+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.VMware, State.Running));
2537+
}
2538+
2539+
@Test(expected = InvalidParameterValueException.class)
2540+
public void checkIfVolumeIsRootAndVmIsRunningRefusesGrowOnRunningKvmSystemVm() {
2541+
VMInstanceVO vm = mockVm(HypervisorType.KVM, State.Running);
2542+
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
2543+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), vm);
2544+
}
2545+
2546+
@Test(expected = InvalidParameterValueException.class)
2547+
public void checkIfVolumeIsRootAndVmIsRunningRefusesGrowOnKvmVmNotRunning() {
2548+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(20L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.KVM, State.Migrating));
2549+
}
2550+
2551+
@Test
2552+
public void checkIfVolumeIsRootAndVmIsRunningAllowsResizeOnStoppedVm() {
2553+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(5L << 30, mockRootVolumeOfSize(10L << 30), mockVm(HypervisorType.VMware, State.Stopped));
2554+
}
2555+
2556+
@Test
2557+
public void checkIfVolumeIsRootAndVmIsRunningIgnoresDataDisk() {
2558+
VolumeVO volume = Mockito.mock(VolumeVO.class);
2559+
Mockito.when(volume.getSize()).thenReturn(10L << 30);
2560+
Mockito.when(volume.getVolumeType()).thenReturn(Volume.Type.DATADISK);
2561+
volumeApiServiceImpl.checkIfVolumeIsRootAndVmIsRunning(5L << 30, volume, Mockito.mock(VMInstanceVO.class));
2562+
}
25072563
}

0 commit comments

Comments
 (0)