Skip to content

Commit 8e3c815

Browse files
engine: keep a migrating instance while its source host may run it
A Migrating instance has its hostId set to the destination and its lastHostId to the source, and either host may be running it until the migration settles. The destroy stop only checked hostId, so with the destination Down it released the resources even though the source host was Up and could still be running the domain. For a destroy, refuse the forced cleanup of a Migrating instance while the source host is not gone. An explicit forced stop is unchanged. Signed-off-by: Brad House <bhouse@nexthop.ai>
1 parent 6a27180 commit 8e3c815

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

‎engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,6 +2434,23 @@ private static boolean isGone(final HostVO host) {
24342434
return host == null || host.getStatus() == Status.Down || host.getStatus() == Status.Removed;
24352435
}
24362436

2437+
/**
2438+
* A Migrating instance has its hostId set to the destination and its lastHostId to the source. Until the
2439+
* migration settles either host may be running it, and a StopCommand only goes to the destination.
2440+
*
2441+
* @return the source host of a migration in progress when that host is not gone, otherwise null
2442+
*/
2443+
protected Long migrationSourceThatMayBeRunningIt(final VMInstanceVO vm) {
2444+
if (vm.getState() != State.Migrating) {
2445+
return null;
2446+
}
2447+
final Long sourceHostId = vm.getLastHostId();
2448+
if (sourceHostId == null || sourceHostId.equals(vm.getHostId()) || isHostGone(sourceHostId)) {
2449+
return null;
2450+
}
2451+
return sourceHostId;
2452+
}
2453+
24372454
/**
24382455
* Whether a stop may release an instance's resources without the host confirming the instance is stopped.
24392456
*/
@@ -2571,6 +2588,13 @@ protected void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUna
25712588
}
25722589
// cleanup() releases the resources whether or not the host answers. For a destroy on a host that is not
25732590
// gone, go ahead only once the host has confirmed the instance is stopped.
2591+
final Long migrationSourceHostId = releaseOnlyIfHostIsGone ? migrationSourceThatMayBeRunningIt(vm) : null;
2592+
if (migrationSourceHostId != null) {
2593+
logger.warn("Not releasing the resources of {} in state {}: the host it is migrating from, {}, may still be running it.",
2594+
vm, vm.getState(), migrationSourceHostId);
2595+
throw new CloudRuntimeException("Unable to stop " + vm + " in state " + vm.getState()
2596+
+ ": the host it is migrating from may still be running it");
2597+
}
25742598
if (releaseOnlyIfHostIsGone && !isHostGone(vm.getHostId())) {
25752599
final Pair<Boolean, String> stopResult = sendStop(vmGuru, profile, false, false);
25762600
if (!stopResult.first()) {

‎engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,60 @@ public void destroyStopOfAStalledInstanceCleansUpWithoutAskingAHostThatIsDown()
22542254
verify(virtualMachineManagerImpl, never()).sendStop(any(), any(), anyBoolean(), anyBoolean());
22552255
}
22562256

2257+
/**
2258+
* An instance stuck in Migrating: hostId is the destination, lastHostId the source. StopRequested is not a
2259+
* transition from Migrating, so advanceStop() goes through the forced cleanup.
2260+
*/
2261+
private VMInstanceVO migratingVm(final Status destinationStatus, final Status sourceStatus) throws Exception {
2262+
final long sourceHostId = hostMockId + 1;
2263+
final HostVO sourceHost = mock(HostVO.class);
2264+
when(sourceHost.getStatus()).thenReturn(sourceStatus);
2265+
when(hostDaoMock.findById(sourceHostId)).thenReturn(sourceHost);
2266+
VMInstanceVO vm = startingVmOnHost(destinationStatus);
2267+
ReflectionTestUtils.setField(vm, "state", State.Migrating);
2268+
ReflectionTestUtils.setField(vm, "lastHostId", sourceHostId);
2269+
return vm;
2270+
}
2271+
2272+
@Test
2273+
public void destroyStopOfAMigratingInstanceFailsWhileTheSourceHostMayStillRunIt() throws Exception {
2274+
for (Status source : new Status[] {Status.Up, Status.Disconnected, Status.Alert}) {
2275+
VMInstanceVO vm = migratingVm(Status.Down, source);
2276+
2277+
CloudRuntimeException e = assertThrows(source.toString(), CloudRuntimeException.class, () -> virtualMachineManagerImpl.advanceStop(vm, true, true));
2278+
2279+
assertTrue(e.getMessage().contains("migrating from"));
2280+
}
2281+
verify(virtualMachineManagerImpl, never()).sendStop(any(), any(), anyBoolean(), anyBoolean());
2282+
verify(virtualMachineManagerImpl, never()).cleanup(any(), any(), any(), any(), anyBoolean());
2283+
}
2284+
2285+
@Test
2286+
public void destroyStopOfAMigratingInstanceCleansUpWhenBothHostsAreGone() throws Exception {
2287+
VMInstanceVO vm = migratingVm(Status.Down, Status.Removed);
2288+
2289+
CloudRuntimeException e = assertThrows(CloudRuntimeException.class, () -> virtualMachineManagerImpl.advanceStop(vm, true, true));
2290+
2291+
assertEquals("cleaned up", e.getMessage());
2292+
}
2293+
2294+
@Test
2295+
public void explicitForcedStopOfAMigratingInstanceIgnoresTheSourceHost() throws Exception {
2296+
VMInstanceVO vm = migratingVm(Status.Down, Status.Up);
2297+
2298+
CloudRuntimeException e = assertThrows(CloudRuntimeException.class, () -> virtualMachineManagerImpl.advanceStop(vm, true, false));
2299+
2300+
assertEquals("cleaned up", e.getMessage());
2301+
}
2302+
2303+
@Test
2304+
public void migrationSourceIsOnlyConsideredForAMigratingInstance() throws Exception {
2305+
VMInstanceVO vm = migratingVm(Status.Up, Status.Up);
2306+
assertEquals(Long.valueOf(hostMockId + 1), virtualMachineManagerImpl.migrationSourceThatMayBeRunningIt(vm));
2307+
ReflectionTestUtils.setField(vm, "state", State.Running);
2308+
assertNull(virtualMachineManagerImpl.migrationSourceThatMayBeRunningIt(vm));
2309+
}
2310+
22572311
@Test
22582312
public void advanceStopQueuesTheFlagOnTheStopJob() throws Exception {
22592313
final AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class);

0 commit comments

Comments
 (0)