diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 8674014addd7..2a28d8af62c7 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -3559,6 +3559,12 @@ private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) { Volume volume = _volsDao.findById(volumeId); VMInstanceVO vm = _vmInstanceDao.findById(vmId); + // A queued detach can run after the volume was already detached and attached elsewhere; leave that attachment alone + if (volume.getInstanceId() == null || volume.getInstanceId() != vmId) { + logger.warn("Volume {} is no longer attached to VM {}, skipping detach", volume, vm); + return volume; + } + String errorMsg = "Failed to detach volume " + volume.getName() + " from VM " + vm.getHostName(); boolean sendCommand = vm.getState() == State.Running; diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index 12258e2cc160..01e91494cda6 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -619,6 +619,30 @@ public void testDetachVolumeFromStoppedXenVm() throws NoSuchFieldException, Ille volumeApiServiceImpl.detachVolumeFromVM(detachCmd); } + @Test + public void testDetachVolumeSkippedWhenAttachedToAnotherVm() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(200L); + Mockito.when(volumeDaoMock.findById(100L)).thenReturn(volume); + + Volume result = volumeApiServiceImpl.detachVolumeViaDestroyVM(300L, 100L); + + Assert.assertEquals(volume, result); + Mockito.verify(volumeDaoMock, Mockito.never()).detachVolume(Mockito.anyLong()); + } + + @Test + public void testDetachVolumeSkippedWhenAlreadyDetached() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(null); + Mockito.when(volumeDaoMock.findById(100L)).thenReturn(volume); + + Volume result = volumeApiServiceImpl.detachVolumeViaDestroyVM(300L, 100L); + + Assert.assertEquals(volume, result); + Mockito.verify(volumeDaoMock, Mockito.never()).detachVolume(Mockito.anyLong()); + } + /** * TESTS FOR ATTACH ROOT VOLUME, COUNT=7 */