diff --git a/server/src/test/java/org/apache/cloudstack/backup/KVMBackupExportServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/backup/KVMBackupExportServiceImplTest.java index fee96ad6453..e75ea528248 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/KVMBackupExportServiceImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/KVMBackupExportServiceImplTest.java @@ -20,7 +20,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,24 +34,39 @@ import java.util.List; import java.util.Map; import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.ImageTransferDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.AccountService; +import com.cloud.user.User; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.dao.VMInstanceDetailsDao; @@ -72,9 +91,36 @@ public class KVMBackupExportServiceImplTest { @Mock ImageTransferDao imageTransferDao; + @Mock + AgentManager agentManager; + + @Mock + HostDao hostDao; + + @Mock + PrimaryDataStoreDao primaryDataStoreDao; + + @Mock + AccountService accountService; + @Mock VirtualMachineManager virtualMachineManager; + @Mock + BackupVO backup; + + @Mock + VolumeVO volume; + + @Mock + HostVO hostVO; + + @Mock + CallContext callContext; + + @Mock + User user; + VMInstanceVO vm; @Before @@ -278,4 +324,135 @@ public class KVMBackupExportServiceImplTest { verify(backupDao).update(10L, backup); verify(backupDao).remove(10L); } + + @Test + public void finalizeImageTransfer_downloadStoppedVm_stopNbdFails_throwsAndDoesNotUpdate() throws Exception { + ImageTransferVO transfer = new ImageTransferVO("tx-download-fail", 11L, 21L, 31L, "sock", + ImageTransferVO.Phase.transferring, ImageTransfer.Direction.download, 1L, 1L, 1L); + ReflectionTestUtils.setField(transfer, "id", 501L); + when(imageTransferDao.findById(501L)).thenReturn(transfer); + when(backupDao.findById(11L)).thenReturn(backup); + when(backup.getHostId()).thenReturn(31L); + when(backup.getVmId()).thenReturn(41L); + when(vmInstanceDao.findById(41L)).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + when(agentManager.send(eq(31L), any(Command.class))).thenReturn( + new Answer(null, true, null), + new Answer(null, false, "stop failed") + ); + + CloudRuntimeException ex = assertThrows(CloudRuntimeException.class, + () -> service.finalizeImageTransfer(501L)); + + assert ex.getMessage().contains("Failed to stop the nbd server"); + verify(imageTransferDao, never()).update(eq(501L), any(ImageTransferVO.class)); + verify(imageTransferDao, never()).remove(501L); + } + + @Test + public void createImageTransfer_uploadFileBackend_success() throws Exception { + ReflectionTestUtils.setField(BackupManager.BackupFrameworkEnabled, "_value", Boolean.FALSE); + try (MockedStatic mocked = mockStatic(CallContext.class)) { + mocked.when(CallContext::current).thenReturn(callContext); + when(callContext.getCallingUser()).thenReturn(user); + + when(volumeDao.findById(601L)).thenReturn(volume); + when(volume.getId()).thenReturn(601L); + when(volume.getDataCenterId()).thenReturn(1L); + when(volume.getPoolId()).thenReturn(701L); + when(volume.getPath()).thenReturn("vol-601.qcow2"); + when(volume.getAccountId()).thenReturn(11L); + when(volume.getDomainId()).thenReturn(12L); + when(imageTransferDao.findByVolume(601L)).thenReturn(null); + + StoragePoolVO pool = new StoragePoolVO(); + pool.setId(701L); + pool.setScope(ScopeType.ZONE); + pool.setDataCenterId(1L); + pool.setPoolType(Storage.StoragePoolType.NetworkFilesystem); + pool.setUuid("pool-701"); + pool.setPath("/primary"); + when(primaryDataStoreDao.findById(701L)).thenReturn(pool); + + when(hostDao.findByDataCenterId(1L)).thenReturn(List.of(hostVO)); + when(hostVO.getId()).thenReturn(801L); + when(hostVO.getDataCenterId()).thenReturn(1L); + when(agentManager.send(eq(801L), any(Command.class))) + .thenReturn(new CreateImageTransferAnswer(null, true, null, "ticket-1", "https://transfer/file")); + + final ImageTransferVO[] persisted = new ImageTransferVO[1]; + when(imageTransferDao.persist(any(ImageTransferVO.class))).thenAnswer(i -> { + persisted[0] = i.getArgument(0); + ReflectionTestUtils.setField(persisted[0], "id", 901L); + return persisted[0]; + }); + when(imageTransferDao.findById(901L)).thenAnswer(i -> persisted[0]); + + ImageTransfer created = service.createImageTransfer(601L, null, ImageTransfer.Direction.upload, ImageTransfer.Format.cow); + + assertNotNull(created); + assertEquals(901L, created.getId()); + assertEquals(ImageTransfer.Direction.upload, created.getDirection()); + verify(agentManager, times(1)).send(eq(801L), any(Command.class)); + } + } + + @Test + public void createImageTransfer_downloadStoppedVm_startsNbdAndCreatesTransfer() throws Exception { + ReflectionTestUtils.setField(BackupManager.BackupFrameworkEnabled, "_value", Boolean.FALSE); + try (MockedStatic mocked = mockStatic(CallContext.class)) { + mocked.when(CallContext::current).thenReturn(callContext); + when(callContext.getCallingUser()).thenReturn(user); + + when(volumeDao.findById(602L)).thenReturn(volume); + when(volume.getId()).thenReturn(602L); + when(volume.getUuid()).thenReturn("vol-602"); + when(volume.getDataCenterId()).thenReturn(1L); + when(volume.getPoolId()).thenReturn(702L); + when(volume.getPath()).thenReturn("vol-602.raw"); + when(imageTransferDao.findByVolume(602L)).thenReturn(null); + + StoragePoolVO pool = new StoragePoolVO(); + pool.setId(702L); + pool.setScope(ScopeType.ZONE); + pool.setDataCenterId(1L); + pool.setPoolType(Storage.StoragePoolType.NetworkFilesystem); + pool.setUuid("pool-702"); + when(primaryDataStoreDao.findById(702L)).thenReturn(pool); + + when(backupDao.findById(612L)).thenReturn(backup); + when(backup.getVmId()).thenReturn(55L); + when(backup.getHostId()).thenReturn(802L); + when(backup.getUuid()).thenReturn("backup-612"); + when(backup.getFromCheckpointId()).thenReturn("ckp-prev"); + when(backup.getAccountId()).thenReturn(11L); + when(backup.getDomainId()).thenReturn(12L); + when(backup.getZoneId()).thenReturn(1L); + when(vmInstanceDao.findById(55L)).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + when(vmInstanceDetailsDao.listDetailsKeyPairs(55L)).thenReturn(Map.of(VmDetailConstants.ACTIVE_CHECKPOINT_ID, "ckp-active")); + when(hostDao.findById(802L)).thenReturn(hostVO); + when(hostVO.getDataCenterId()).thenReturn(1L); + + when(agentManager.send(eq(802L), any(Command.class))).thenReturn( + new StartNBDServerAnswer(null, true, null), + new CreateImageTransferAnswer(null, true, null, "ticket-2", "https://transfer/nbd") + ); + + final ImageTransferVO[] persisted = new ImageTransferVO[1]; + when(imageTransferDao.persist(any(ImageTransferVO.class))).thenAnswer(i -> { + persisted[0] = i.getArgument(0); + ReflectionTestUtils.setField(persisted[0], "id", 902L); + return persisted[0]; + }); + when(imageTransferDao.findById(902L)).thenAnswer(i -> persisted[0]); + + ImageTransfer created = service.createImageTransfer(602L, 612L, ImageTransfer.Direction.download, ImageTransfer.Format.raw); + + assertNotNull(created); + assertEquals(902L, created.getId()); + assertEquals(ImageTransfer.Direction.download, created.getDirection()); + verify(agentManager, times(2)).send(eq(802L), any(Command.class)); + } + } }