This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new 7ea1dca5167 Add NULL check during restoreVM operation when host is 
removed (#13779)
7ea1dca5167 is described below

commit 7ea1dca5167ba46c3ef812826b7bfb4083bf408f
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Mon Aug 24 15:26:29 2026 -0300

    Add NULL check during restoreVM operation when host is removed (#13779)
    
    * Add NULL check during restoreVM operation when host is removed (#571)
    
    * Add NULL check during restore VM operation when host is not 
available/removed
    
    * fix line ending pre commit failure
    
    * update logging with details of removed host and vm
    
    ---------
    
    Co-authored-by: Sachin R Doddaguni <[email protected]>
    (cherry picked from commit 1dbda12ccdca1aaf86ef8f9b185986c9d22b567e)
    
    * Handle null host in VM restore to prevent NPE on deleted host records
    
    * Fix build
    
    * Fix unit test
    
    ---------
    
    Co-authored-by: Sachin R <[email protected]>
    Co-authored-by: Sachin R Doddaguni <[email protected]>
    Co-authored-by: mprokopchuk <[email protected]>
---
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 17 ++--
 .../java/com/cloud/vm/UserVmManagerImplTest.java   | 97 ++++++++++++++++++++++
 2 files changed, 109 insertions(+), 5 deletions(-)

diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 857c2b05af4..b5843ad042f 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -9275,17 +9275,24 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             Long hostId = vm.getHostId() != null ? vm.getHostId() : 
vm.getLastHostId();
 
             if (hostId != null) {
-                // default findById() won't search entries with removed field 
not null
-                Host host = _hostDao.findById(hostId);
+                Host host = _hostDao.findByIdIncludingRemoved(hostId);
+
+                // host row may have been hard-deleted from DB, treat like 
removed
                 if (host == null) {
-                    logger.warn("Host {} not found", hostId);
+                    logger.warn(String.format("Host with id {} not found in DB 
for VM %s ({})",
+                            hostId, vm.getUuid(), vm.getName()));
+                    return;
+                }
+                // host could be in removed state, in which case no operation 
is performed.
+                if (host.getStatus() == Status.Removed) {
+                    logger.warn("Host {} ({}) for VM {} ({}) removed on {}",
+                            host.getUuid(), host.getName(), vm.getUuid(), 
vm.getName(), host.getRemoved());
                     return;
                 }
-
-                VolumeInfo volumeInfo = volFactory.getVolume(root.getId());
 
                 final Command cmd;
 
+                VolumeInfo volumeInfo = volFactory.getVolume(root.getId());
                 if (host.getHypervisorType() == HypervisorType.XenServer) {
                     DiskTO disk = new DiskTO(volumeInfo.getTO(), 
root.getDeviceId(), root.getPath(), root.getVolumeType());
 
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java 
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index cbedd10825e..8e5f97ee347 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -59,6 +59,8 @@ import java.util.Map;
 import java.util.TimeZone;
 import java.util.UUID;
 
+import com.cloud.event.dao.UsageEventDao;
+import com.cloud.host.Status;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -82,6 +84,7 @@ import org.apache.cloudstack.backup.dao.BackupDao;
 import org.apache.cloudstack.backup.dao.BackupScheduleDao;
 import org.apache.cloudstack.context.CallContext;
 import 
org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import 
org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.resourcelimit.Reserver;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
@@ -89,6 +92,8 @@ import 
org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.storage.template.VnfTemplateManager;
 import org.apache.cloudstack.userdata.UserDataManager;
 import org.apache.cloudstack.vm.UnmanagedVMsManager;
@@ -426,6 +431,15 @@ public class UserVmManagerImplTest {
     @Mock
     private VolumeDataFactory volumeDataFactory;
 
+    @Mock
+    private VolumeOrchestrationService volumeMgr;
+
+    @Mock
+    private TemplateDataStoreDao templateDataStoreDao;
+
+    @Mock
+    private UsageEventDao usageEventDao;
+
     @Mock
     private VolumeInfo volumeInfo;
 
@@ -1664,6 +1678,89 @@ public class UserVmManagerImplTest {
         
Mockito.verify(userVmManagerImpl).addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecified(Mockito.any(),
 Mockito.any(), Mockito.eq(VmDetailConstants.CPU_NUMBER), Mockito.any());
     }
 
+    @Test
+    public void testRestoreVirtualMachineWhenHostRemoved() throws 
ResourceUnavailableException, InsufficientCapacityException, 
ResourceAllocationException {
+        long vmId = 1L;
+        Long lastHostId = 42L;
+        Long newTemplateId = 2L;
+        boolean expunge = false;
+        Map<String, String> details = new HashMap<>();
+
+        try (MockedStatic<CallContext> ignored = 
Mockito.mockStatic(CallContext.class);
+             MockedStatic<UsageEventUtils> ignoredEventUtils = 
mockStatic(UsageEventUtils.class)
+        ) {
+            UserVmVO vm = mock(UserVmVO.class);
+            when(vm.getId()).thenReturn(vmId);
+            when(vm.getAccountId()).thenReturn(accountId);
+            when(vm.getHostId()).thenReturn(null);
+            when(vm.getLastHostId()).thenReturn(lastHostId);
+            when(vm.getUuid()).thenReturn("test-uuid");
+            when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
+            when(vm.getTemplateId()).thenReturn(1L);
+            when(vm.getDataCenterId()).thenReturn(1L);
+
+            CallContext mockCallContext = mock(CallContext.class);
+            when(mockCallContext.getCallingAccount()).thenReturn(accountMock);
+
+            CallContext mockVolumeContext = mock(CallContext.class);
+            when(CallContext.register(any(CallContext.class), 
any(ApiCommandResourceType.class))).thenReturn(mockVolumeContext);
+
+            when(CallContext.current()).thenReturn(mockCallContext);
+            when(accountDao.findById(accountId)).thenReturn(callerAccount);
+            
when(accountDao.findByIdIncludingRemoved(accountId)).thenReturn(callerAccount);
+            when(callerAccount.getState()).thenReturn(Account.State.ENABLED);
+            VMTemplateVO template = mock(VMTemplateVO.class);
+            when(templateDao.findById(anyLong())).thenReturn(template);
+            when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
+            when(template.getId()).thenReturn(1L);
+            when(template.isDirectDownload()).thenReturn(false);
+            when(template.getSize()).thenReturn(10L * 1024 * 1024 * 1024L); // 
10GB
+
+            TemplateDataStoreVO templateStore = 
mock(TemplateDataStoreVO.class);
+            when(templateDataStoreDao.findByTemplateZoneReady(1L, 
1L)).thenReturn(templateStore);
+
+            ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class);
+            when(vm.getServiceOfferingId()).thenReturn(serviceOfferingId);
+
+            List<VolumeVO> rootVols = new ArrayList<>();
+            VolumeVO rootVol = mock(VolumeVO.class);
+            when(rootVol.getId()).thenReturn(10L);
+            when(rootVol.getState()).thenReturn(Volume.State.Ready);
+            when(rootVol.getPoolId()).thenReturn(5L);
+            when(rootVol.getTemplateId()).thenReturn(1L);
+            when(rootVol.getSize()).thenReturn(20L * 1024 * 1024 * 1024L); // 
20GB
+            when(rootVol.getDiskOfferingId()).thenReturn(100L);
+            when(rootVol.isDisplay()).thenReturn(true);
+            rootVols.add(rootVol);
+            DiskOfferingVO diskOffering = mock(DiskOfferingVO.class);
+            when(diskOfferingDao.findById(100L)).thenReturn(diskOffering);
+
+            StoragePoolVO storagePool = mock(StoragePoolVO.class);
+            when(storagePool.isManaged()).thenReturn(true);
+            when(primaryDataStoreDao.findById(5L)).thenReturn(storagePool);
+            when(vmSnapshotDaoMock.findByVm(vmId)).thenReturn(new 
ArrayList<>());
+            when(volumeDaoMock.findByInstanceAndType(vmId, 
Volume.Type.ROOT)).thenReturn(rootVols);
+            when(volumeDaoMock.findById(anyLong())).thenReturn(rootVol);
+            when(userVmDao.findById(vmId)).thenReturn(vm);
+
+            HostVO host = mock(HostVO.class);
+            when(host.getStatus()).thenReturn(Status.Removed);
+            
when(hostDao.findByIdIncludingRemoved(lastHostId)).thenReturn(host);
+            VolumeInfo volumeInfo = mock(VolumeInfo.class);
+
+            VolumeVO newVolume = mock(VolumeVO.class);
+            when(volumeMgr.allocateDuplicateVolume(any(VolumeVO.class), any(), 
anyLong()))
+                    .thenReturn(newVolume);
+            when(newVolume.getId()).thenReturn(11L);
+
+            UserVm result = 
userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, 
expunge, details);
+            assertNotNull(result);
+
+            Mockito.verify(userVmDao).findById(vmId);
+            Mockito.verify(hostDao).findByIdIncludingRemoved(lastHostId);
+        }
+    }
+
     @Test
     public void testCheckVolumesLimits() {
         long diskOffId1 = 1L;

Reply via email to