Skip to content

Commit 767d37d

Browse files
Mark VMs in error state when expunge fails during destroy operation
1 parent 65e5409 commit 767d37d

3 files changed

Lines changed: 84 additions & 2 deletions

File tree

api/src/main/java/com/cloud/vm/VirtualMachine.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ public static StateMachine2<State, VirtualMachine.Event, VirtualMachine> getStat
124124
s_fsm.addTransition(new Transition<State, Event>(State.Stopping, VirtualMachine.Event.StopRequested, State.Stopping, null));
125125
s_fsm.addTransition(new Transition<State, Event>(State.Stopping, VirtualMachine.Event.AgentReportShutdowned, State.Stopped, Arrays.asList(new Impact[]{Impact.USAGE})));
126126
s_fsm.addTransition(new Transition<State, Event>(State.Expunging, VirtualMachine.Event.OperationFailed, State.Expunging,null));
127+
// Note: In addition to the Stopped -> Error transition for failed VM creation,
128+
// a VM can also transition from Expunging to Error on OperationFailedToError.
129+
s_fsm.addTransition(new Transition<State, Event>(State.Expunging, VirtualMachine.Event.OperationFailedToError, State.Error, null));
127130
s_fsm.addTransition(new Transition<State, Event>(State.Expunging, VirtualMachine.Event.ExpungeOperation, State.Expunging,null));
128131
s_fsm.addTransition(new Transition<State, Event>(State.Error, VirtualMachine.Event.DestroyRequested, State.Expunging, null));
129132
s_fsm.addTransition(new Transition<State, Event>(State.Error, VirtualMachine.Event.ExpungeOperation, State.Expunging, null));

server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2578,6 +2578,22 @@ public boolean expunge(UserVmVO vm) {
25782578
}
25792579
}
25802580

2581+
private void transitionExpungingToError(long vmId) {
2582+
try {
2583+
UserVmVO vm = _vmDao.findById(vmId);
2584+
if (vm != null && vm.getState() == State.Expunging) {
2585+
boolean transitioned = _itMgr.stateTransitTo(vm, VirtualMachine.Event.OperationFailedToError, null);
2586+
if (transitioned) {
2587+
logger.info("Transitioned VM [{}] from Expunging to Error after failed expunge", vm.getUuid());
2588+
} else {
2589+
logger.warn("Failed to persist transition of VM [{}] from Expunging to Error after failed expunge, possibly due to concurrent update", vm.getUuid());
2590+
}
2591+
}
2592+
} catch (NoTransitionException e) {
2593+
logger.warn("Failed to transition VM to Error state: {}", e.getMessage());
2594+
}
2595+
}
2596+
25812597
/**
25822598
* Release network resources, it was done on vm stop previously.
25832599
* @param id vm id
@@ -3561,8 +3577,19 @@ public UserVm destroyVm(DestroyVMCmd cmd) throws ResourceUnavailableException, C
35613577
detachVolumesFromVm(vm, dataVols);
35623578

35633579
UserVm destroyedVm = destroyVm(vmId, expunge);
3564-
if (expunge && !expunge(vm)) {
3565-
throw new CloudRuntimeException("Failed to expunge vm " + destroyedVm);
3580+
if (expunge) {
3581+
boolean expunged;
3582+
try {
3583+
expunged = expunge(vm);
3584+
} catch (RuntimeException e) {
3585+
logger.error("Failed to expunge VM [{}] due to: {}", vm, e.getMessage(), e);
3586+
transitionExpungingToError(vm.getId());
3587+
throw new CloudRuntimeException("Failed to expunge VM " + vm.getUuid() + " due to: " + e.getMessage(), e);
3588+
}
3589+
if (!expunged) {
3590+
transitionExpungingToError(vm.getId());
3591+
throw new CloudRuntimeException("Failed to expunge VM " + destroyedVm);
3592+
}
35663593
}
35673594

35683595
autoScaleManager.removeVmFromVmGroup(vmId);

server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import java.util.UUID;
6161

6262
import com.cloud.storage.dao.SnapshotPolicyDao;
63+
import com.cloud.utils.fsm.NoTransitionException;
6364
import org.apache.cloudstack.acl.ControlledEntity;
6465
import org.apache.cloudstack.acl.SecurityChecker;
6566
import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -4177,4 +4178,55 @@ public void testUnmanageUserVMSuccess() {
41774178
verify(userVmDao, times(1)).releaseFromLockTable(vmId);
41784179
}
41794180

4181+
@Test
4182+
public void testTransitionExpungingToErrorVmInExpungingState() throws Exception {
4183+
UserVmVO vm = mock(UserVmVO.class);
4184+
when(vm.getState()).thenReturn(VirtualMachine.State.Expunging);
4185+
when(vm.getUuid()).thenReturn("test-uuid");
4186+
when(userVmDao.findById(vmId)).thenReturn(vm);
4187+
when(virtualMachineManager.stateTransitTo(eq(vm), eq(VirtualMachine.Event.OperationFailedToError), eq(null))).thenReturn(true);
4188+
4189+
java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class);
4190+
method.setAccessible(true);
4191+
method.invoke(userVmManagerImpl, vmId);
4192+
4193+
Mockito.verify(virtualMachineManager).stateTransitTo(vm, VirtualMachine.Event.OperationFailedToError, null);
4194+
}
4195+
4196+
@Test
4197+
public void testTransitionExpungingToErrorVmNotInExpungingState() throws Exception {
4198+
UserVmVO vm = mock(UserVmVO.class);
4199+
when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
4200+
when(userVmDao.findById(vmId)).thenReturn(vm);
4201+
4202+
java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class);
4203+
method.setAccessible(true);
4204+
method.invoke(userVmManagerImpl, vmId);
4205+
4206+
Mockito.verify(virtualMachineManager, Mockito.never()).stateTransitTo(any(VirtualMachine.class), any(VirtualMachine.Event.class), any());
4207+
}
4208+
4209+
@Test
4210+
public void testTransitionExpungingToErrorVmNotFound() throws Exception {
4211+
when(userVmDao.findById(vmId)).thenReturn(null);
4212+
4213+
java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class);
4214+
method.setAccessible(true);
4215+
method.invoke(userVmManagerImpl, vmId);
4216+
4217+
Mockito.verify(virtualMachineManager, Mockito.never()).stateTransitTo(any(VirtualMachine.class), any(VirtualMachine.Event.class), any());
4218+
}
4219+
4220+
@Test
4221+
public void testTransitionExpungingToErrorHandlesNoTransitionException() throws Exception {
4222+
UserVmVO vm = mock(UserVmVO.class);
4223+
when(vm.getState()).thenReturn(VirtualMachine.State.Expunging);
4224+
when(userVmDao.findById(vmId)).thenReturn(vm);
4225+
when(virtualMachineManager.stateTransitTo(eq(vm), eq(VirtualMachine.Event.OperationFailedToError), eq(null)))
4226+
.thenThrow(new NoTransitionException("no transition"));
4227+
4228+
java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class);
4229+
method.setAccessible(true);
4230+
method.invoke(userVmManagerImpl, vmId);
4231+
}
41804232
}

0 commit comments

Comments
 (0)