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
17 changes: 17 additions & 0 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,22 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
}
}

/**
* The resize orchestration (orchestrateResizeVolume) only tells the hypervisor about a running user VM. For a
* system VM the volume is resized on the storage, but the running guest is not told and keeps seeing the old size
* until it next starts. So a system VM's volume can only be resized while the system VM is stopped.
*/
protected void validateSystemVmIsStoppedForResize(VolumeVO volume) {
if (volume.getInstanceId() == null) {
return;
}
VMInstanceVO vm = _vmInstanceDao.findById(volume.getInstanceId());
if (vm != null && vm.getType().isUsedBySystem() && !State.Stopped.equals(vm.getState())) {
throw new InvalidParameterValueException(String.format("Cannot resize volume [%s] of system VM %s while it is in state %s. "
+ "Stop the system VM before resizing its volume.", volume.getName(), vm.getInstanceName(), vm.getState()));
}
}

protected void validateNoVmSnapshots(VolumeVO volume) {
if (volume.getInstanceId() != null) {
if (vmHasVmSnapshotsExceptKvmDiskOnlySnapshots(volume.getInstanceId())) {
Expand Down Expand Up @@ -2496,6 +2512,7 @@ private void validateVolumeResizeWithSize(VolumeVO volume, long currentSize, Lon
// if the caller is looking to change the size of the volume
if (newSize != null && currentSize != newSize) {
validateNoVmSnapshots(volume);
validateSystemVmIsStoppedForResize(volume);

if (!validateVolumeSizeInBytes(newSize)) {
throw new InvalidParameterValueException("Requested size out of range");
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 @@ -2267,6 +2268,78 @@ public void validateNoVmSnapshotsTestVmHasVmSnapshotsExceptKvmDiskOnlySnapshotsI
volumeApiServiceImpl.validateNoVmSnapshots(volumeVoMock);
}

private VMInstanceVO mockVmInstance(VirtualMachine.Type type, State state) {
VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
Mockito.when(vm.getType()).thenReturn(type);
Mockito.lenient().when(vm.getState()).thenReturn(state);
Mockito.when(volumeVoMock.getInstanceId()).thenReturn(vmInstanceMockId);
Mockito.when(_vmInstanceDao.findById(vmInstanceMockId)).thenReturn(vm);
return vm;
}

@Test
public void validateSystemVmIsStoppedForResizeTestDetachedVolume() {
Mockito.when(volumeVoMock.getInstanceId()).thenReturn(null);

volumeApiServiceImpl.validateSystemVmIsStoppedForResize(volumeVoMock);

Mockito.verify(_vmInstanceDao, Mockito.never()).findById(Mockito.anyLong());
}

@Test
public void validateSystemVmIsStoppedForResizeTestRunningUserVm() {
mockVmInstance(VirtualMachine.Type.User, State.Running);

volumeApiServiceImpl.validateSystemVmIsStoppedForResize(volumeVoMock);
}

@Test
public void validateSystemVmIsStoppedForResizeTestStoppedRouter() {
mockVmInstance(VirtualMachine.Type.DomainRouter, State.Stopped);

volumeApiServiceImpl.validateSystemVmIsStoppedForResize(volumeVoMock);
}

@Test
public void validateSystemVmIsStoppedForResizeTestNotStoppedSystemVms() {
for (VirtualMachine.Type type : List.of(VirtualMachine.Type.DomainRouter, VirtualMachine.Type.ConsoleProxy,
VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.InternalLoadBalancerVm)) {
for (State state : List.of(State.Running, State.Starting, State.Stopping, State.Migrating)) {
mockVmInstance(type, state);
try {
volumeApiServiceImpl.validateSystemVmIsStoppedForResize(volumeVoMock);
Assert.fail("Resizing the volume of a " + type + " in state " + state + " must be refused");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage(), e.getMessage().contains("Stop the system VM"));
}
}
}
}

@Test
public void resizeVolumeSizeOnlyOnRunningRouterIsRefused() throws ResourceAllocationException {
ResizeVolumeCmd cmd = new ResizeVolumeCmd();
ReflectionTestUtils.setField(cmd, "id", volumeMockId);
ReflectionTestUtils.setField(cmd, "size", 1000L); // GiB, a grow of the 425 GiB volumeVoMock

Mockito.when(volumeDaoMock.findById(volumeMockId)).thenReturn(volumeVoMock);
Mockito.when(volumeDaoMock.getHypervisorType(volumeMockId)).thenReturn(HypervisorType.KVM);
Mockito.when(volumeVoMock.getState()).thenReturn(Volume.State.Ready);
Mockito.when(volumeVoMock.getDiskOfferingId()).thenReturn(diskOfferingMockId);
Mockito.when(_diskOfferingDao.findById(diskOfferingMockId)).thenReturn(diskOfferingMock);
Mockito.doReturn(false).when(volumeApiServiceImpl).isNotPossibleToResize(volumeVoMock, diskOfferingMock);
Mockito.when(volumeDataFactoryMock.getVolume(volumeMockId)).thenReturn(volumeInfoMock);
mockVmInstance(VirtualMachine.Type.DomainRouter, State.Running);

try {
volumeApiServiceImpl.resizeVolume(cmd);
Assert.fail("A size-only resize of a running router's volume must be refused");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage(), e.getMessage().contains("Stop the system VM"));
}
Mockito.verify(volumeServiceMock, Mockito.never()).resize(any(VolumeInfo.class));
}

@Test
public void vmHasVmSnapshotsExceptKvmDiskOnlySnapshotsTestMultipleDiskAndMemory () {
List<VMSnapshotVO> snapList = generateVmSnapshotVoList(VMSnapshot.Type.DiskAndMemory, VMSnapshot.Type.DiskAndMemory);
Expand Down
Loading