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

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


The following commit(s) were added to refs/heads/4.19 by this push:
     new a627ab67c29 server: fix pod retrieval during volume attach (#10324)
a627ab67c29 is described below

commit a627ab67c298345e92da013ebc0b786918a85f96
Author: Abhishek Kumar <[email protected]>
AuthorDate: Fri Feb 7 17:29:23 2025 +0530

    server: fix pod retrieval during volume attach (#10324)
    
    Signed-off-by: Abhishek Kumar <[email protected]>
---
 .../com/cloud/storage/VolumeApiServiceImpl.java    | 125 +++++----
 .../cloud/storage/VolumeApiServiceImplTest.java    | 280 +++++++++++++++++++++
 2 files changed, 363 insertions(+), 42 deletions(-)

diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java 
b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index 7f867eb01a9..3ea8116764a 100644
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@ -133,7 +133,9 @@ import com.cloud.dc.ClusterDetailsDao;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.Pod;
+import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.HostPodDao;
 import com.cloud.domain.Domain;
 import com.cloud.domain.dao.DomainDao;
 import com.cloud.event.ActionEvent;
@@ -153,6 +155,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.HypervisorCapabilitiesVO;
 import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
 import com.cloud.offering.DiskOffering;
+import com.cloud.org.Cluster;
 import com.cloud.org.Grouping;
 import com.cloud.projects.Project;
 import com.cloud.projects.ProjectManager;
@@ -323,6 +326,8 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
     @Inject
     private VmWorkJobDao _workJobDao;
     @Inject
+    ClusterDao clusterDao;
+    @Inject
     private ClusterDetailsDao _clusterDetailsDao;
     @Inject
     private StorageManager storageMgr;
@@ -346,6 +351,8 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
     protected ProjectManager projectManager;
     @Inject
     protected StoragePoolDetailsDao storagePoolDetailsDao;
+    @Inject
+    HostPodDao podDao;
 
 
     protected Gson _gson;
@@ -2380,17 +2387,10 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
         return attachVolumeToVM(command.getVirtualMachineId(), 
command.getId(), command.getDeviceId());
     }
 
-    private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long 
deviceId) {
-        VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
-
-        if (volumeToAttach.isAttachedVM()) {
-            throw new CloudRuntimeException("This volume is already attached 
to a VM.");
-        }
-
-        UserVmVO vm = _userVmDao.findById(vmId);
+    protected VolumeVO getVmExistingVolumeForVolumeAttach(UserVmVO vm, 
VolumeInfo volumeToAttach) {
         VolumeVO existingVolumeOfVm = null;
         VMTemplateVO template = _templateDao.findById(vm.getTemplateId());
-        List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vmId, 
Volume.Type.ROOT);
+        List<VolumeVO> rootVolumesOfVm = 
_volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);
         if (rootVolumesOfVm.size() > 1 && template != null && 
!template.isDeployAsIs()) {
             throw new CloudRuntimeException("The VM " + vm.getHostName() + " 
has more than one ROOT volume and is in an invalid state.");
         } else {
@@ -2398,7 +2398,7 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
                 existingVolumeOfVm = rootVolumesOfVm.get(0);
             } else {
                 // locate data volume of the vm
-                List<VolumeVO> diskVolumesOfVm = 
_volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK);
+                List<VolumeVO> diskVolumesOfVm = 
_volsDao.findByInstanceAndType(vm.getId(), Volume.Type.DATADISK);
                 for (VolumeVO diskVolume : diskVolumesOfVm) {
                     if (diskVolume.getState() != Volume.State.Allocated) {
                         existingVolumeOfVm = diskVolume;
@@ -2407,45 +2407,91 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
                 }
             }
         }
-        if (s_logger.isTraceEnabled()) {
-            String msg = "attaching volume %s/%s to a VM (%s/%s) with an 
existing volume %s/%s on primary storage %s";
-            if (existingVolumeOfVm != null) {
-                s_logger.trace(String.format(msg,
-                        volumeToAttach.getName(), volumeToAttach.getUuid(),
+        if (existingVolumeOfVm == null) {
+            if (s_logger.isTraceEnabled()) {
+                s_logger.trace(String.format("No existing volume found for VM 
(%s/%s) to attach volume %s/%s",
                         vm.getName(), vm.getUuid(),
-                        existingVolumeOfVm.getName(), 
existingVolumeOfVm.getUuid(),
-                        existingVolumeOfVm.getPoolId()));
+                        volumeToAttach.getName(), volumeToAttach.getUuid()));
             }
+            return null;
         }
-
-        HypervisorType rootDiskHyperType = vm.getHypervisorType();
-        HypervisorType volumeToAttachHyperType = 
_volsDao.getHypervisorType(volumeToAttach.getId());
-
+        if (s_logger.isTraceEnabled()) {
+            String msg = "attaching volume %s/%s to a VM (%s/%s) with an 
existing volume %s/%s on primary storage %s";
+            s_logger.trace(String.format(msg,
+                    volumeToAttach.getName(), volumeToAttach.getUuid(),
+                    vm.getName(), vm.getUuid(),
+                    existingVolumeOfVm.getName(), existingVolumeOfVm.getUuid(),
+                    existingVolumeOfVm.getPoolId()));
+        }
+        return existingVolumeOfVm;
+    }
+
+    protected StoragePool 
getSuitablePoolForAllocatedOrUploadedVolumeForAttach(final VolumeInfo 
volumeToAttach, final UserVmVO vm) {
+        DataCenter zone = _dcDao.findById(vm.getDataCenterId());
+        Pair<Long, Long> clusterHostId = 
virtualMachineManager.findClusterAndHostIdForVm(vm, false);
+        Long podId = vm.getPodIdToDeployIn();
+        if (clusterHostId.first() != null) {
+            Cluster cluster = clusterDao.findById(clusterHostId.first());
+            podId = cluster.getPodId();
+        }
+        Pod pod = podDao.findById(podId);
+        DiskOfferingVO offering = 
_diskOfferingDao.findById(volumeToAttach.getDiskOfferingId());
+        DiskProfile diskProfile =  new DiskProfile(volumeToAttach.getId(), 
volumeToAttach.getVolumeType(),
+                volumeToAttach.getName(), volumeToAttach.getId(), 
volumeToAttach.getSize(), offering.getTagsArray(),
+                offering.isUseLocalStorage(), offering.isRecreatable(),
+                volumeToAttach.getTemplateId());
+        diskProfile.setHyperType(vm.getHypervisorType());
+        return _volumeMgr.findStoragePool(diskProfile, zone, pod, 
clusterHostId.first(),
+                clusterHostId.second(), vm, Collections.emptySet());
+    }
+
+    protected VolumeInfo createVolumeOnPrimaryForAttachIfNeeded(final 
VolumeInfo volumeToAttach, final UserVmVO vm, VolumeVO existingVolumeOfVm) {
         VolumeInfo newVolumeOnPrimaryStorage = volumeToAttach;
-
+        boolean volumeOnSecondary = volumeToAttach.getState() == 
Volume.State.Uploaded;
+        if (!Arrays.asList(Volume.State.Allocated, 
Volume.State.Uploaded).contains(volumeToAttach.getState())) {
+            return newVolumeOnPrimaryStorage;
+        }
         //don't create volume on primary storage if its being attached to the 
vm which Root's volume hasn't been created yet
-        StoragePoolVO destPrimaryStorage = null;
+        StoragePool destPrimaryStorage = null;
         if (existingVolumeOfVm != null && 
!existingVolumeOfVm.getState().equals(Volume.State.Allocated)) {
             destPrimaryStorage = 
_storagePoolDao.findById(existingVolumeOfVm.getPoolId());
             if (s_logger.isTraceEnabled() && destPrimaryStorage != null) {
                 s_logger.trace(String.format("decided on target storage: 
%s/%s", destPrimaryStorage.getName(), destPrimaryStorage.getUuid()));
             }
         }
-
-        boolean volumeOnSecondary = volumeToAttach.getState() == 
Volume.State.Uploaded;
-
-        if (destPrimaryStorage != null && (volumeToAttach.getState() == 
Volume.State.Allocated || volumeOnSecondary)) {
-            try {
-                if (volumeOnSecondary && destPrimaryStorage.getPoolType() == 
Storage.StoragePoolType.PowerFlex) {
-                    throw new InvalidParameterValueException("Cannot attach 
uploaded volume, this operation is unsupported on storage pool type " + 
destPrimaryStorage.getPoolType());
+        if (destPrimaryStorage == null) {
+            destPrimaryStorage = 
getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+            if (destPrimaryStorage == null) {
+                if (Volume.State.Allocated.equals(volumeToAttach.getState()) 
&& State.Stopped.equals(vm.getState())) {
+                    return newVolumeOnPrimaryStorage;
                 }
-                newVolumeOnPrimaryStorage = 
_volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach, rootDiskHyperType, 
destPrimaryStorage);
-            } catch (NoTransitionException e) {
-                s_logger.debug("Failed to create volume on primary storage", 
e);
-                throw new CloudRuntimeException("Failed to create volume on 
primary storage", e);
+                throw new CloudRuntimeException(String.format("Failed to find 
a primary storage for volume in state: %s", volumeToAttach.getState()));
+            }
+        }
+        try {
+            if (volumeOnSecondary && 
Storage.StoragePoolType.PowerFlex.equals(destPrimaryStorage.getPoolType())) {
+                throw new InvalidParameterValueException("Cannot attach 
uploaded volume, this operation is unsupported on storage pool type " + 
destPrimaryStorage.getPoolType());
             }
+            newVolumeOnPrimaryStorage = 
_volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach,
+                    vm.getHypervisorType(), destPrimaryStorage);
+        } catch (NoTransitionException e) {
+            s_logger.debug("Failed to create volume on primary storage", e);
+            throw new CloudRuntimeException("Failed to create volume on 
primary storage", e);
+        }
+        return newVolumeOnPrimaryStorage;
+    }
+
+    private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long 
deviceId) {
+        VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
+
+        if (volumeToAttach.isAttachedVM()) {
+            throw new CloudRuntimeException("This volume is already attached 
to a VM.");
         }
 
+        UserVmVO vm = _userVmDao.findById(vmId);
+        VolumeVO existingVolumeOfVm = getVmExistingVolumeForVolumeAttach(vm, 
volumeToAttach);
+        VolumeInfo newVolumeOnPrimaryStorage = 
createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, existingVolumeOfVm);
+
         // reload the volume from db
         newVolumeOnPrimaryStorage = 
volFactory.getVolume(newVolumeOnPrimaryStorage.getId());
         boolean moveVolumeNeeded = needMoveVolume(existingVolumeOfVm, 
newVolumeOnPrimaryStorage);
@@ -2463,19 +2509,17 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
             StoragePoolVO vmRootVolumePool = 
_storagePoolDao.findById(existingVolumeOfVm.getPoolId());
 
             try {
+                HypervisorType volumeToAttachHyperType = 
_volsDao.getHypervisorType(volumeToAttach.getId());
                 newVolumeOnPrimaryStorage = 
_volumeMgr.moveVolume(newVolumeOnPrimaryStorage, 
vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), 
vmRootVolumePool.getClusterId(),
                         volumeToAttachHyperType);
-            } catch (ConcurrentOperationException e) {
-                s_logger.debug("move volume failed", e);
-                throw new CloudRuntimeException("move volume failed", e);
-            } catch (StorageUnavailableException e) {
+            } catch (ConcurrentOperationException | 
StorageUnavailableException e) {
                 s_logger.debug("move volume failed", e);
                 throw new CloudRuntimeException("move volume failed", e);
             }
         }
         VolumeVO newVol = _volsDao.findById(newVolumeOnPrimaryStorage.getId());
         // Getting the fresh vm object in case of volume migration to check 
the current state of VM
-        if (moveVolumeNeeded || volumeOnSecondary) {
+        if (moveVolumeNeeded) {
             vm = _userVmDao.findById(vmId);
             if (vm == null) {
                 throw new InvalidParameterValueException("VM not found.");
@@ -2659,9 +2703,6 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
             if (!_volsDao.findByInstanceAndDeviceId(vm.getId(), 0).isEmpty()) {
                 throw new InvalidParameterValueException("Vm already has root 
volume attached to it");
             }
-            if (volumeToAttach.getState() == Volume.State.Uploaded) {
-                throw new InvalidParameterValueException("No support for Root 
volume attach in state " + Volume.State.Uploaded);
-            }
         }
     }
 
diff --git 
a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java 
b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
index a0f89956df5..9b087bd384b 100644
--- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
+++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
@@ -45,6 +45,7 @@ import 
org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
 import org.apache.cloudstack.context.CallContext;
+import 
org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
@@ -86,8 +87,12 @@ import org.springframework.test.util.ReflectionTestUtils;
 import com.cloud.api.query.dao.ServiceOfferingJoinDao;
 import com.cloud.configuration.Resource;
 import com.cloud.configuration.Resource.ResourceType;
+import com.cloud.dc.ClusterVO;
 import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.HostPodVO;
+import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.HostPodDao;
 import com.cloud.event.EventTypes;
 import com.cloud.event.UsageEventUtils;
 import com.cloud.exception.InvalidParameterValueException;
@@ -122,10 +127,12 @@ import com.cloud.utils.Pair;
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
+import com.cloud.vm.DiskProfile;
 import com.cloud.vm.UserVmManager;
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachine.State;
+import com.cloud.vm.VirtualMachineManager;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.VMSnapshotVO;
@@ -199,6 +206,15 @@ public class VolumeApiServiceImplTest {
     private DataStoreManager dataStoreMgr;
     @Mock
     private SnapshotHelper snapshotHelper;
+    @Mock
+    VirtualMachineManager virtualMachineManager;
+    @Mock
+    HostPodDao podDao;
+    @Mock
+    ClusterDao clusterDao;
+    @Mock
+    VolumeOrchestrationService volumeOrchestrationService;
+
 
     private DetachVolumeCmd detachCmd = new DetachVolumeCmd();
     private Class<?> _detachCmdClass = detachCmd.getClass();
@@ -1820,4 +1836,268 @@ public class VolumeApiServiceImplTest {
 
         volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
     }
+
+    private UserVmVO getMockedVm() {
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        Mockito.when(vm.getId()).thenReturn(1L);
+        Mockito.when(vm.getTemplateId()).thenReturn(10L);
+        Mockito.when(vm.getHostName()).thenReturn("test-vm");
+        return vm;
+    }
+
+    private VMTemplateVO getMockedTemplate() {
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        Mockito.when(template.isDeployAsIs()).thenReturn(false);
+        return template;
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void 
testGetVmExistingVolumeForVolumeAttach_MultipleRootVolumes_ThrowsException() {
+        UserVmVO vm = getMockedVm();
+        VMTemplateVO template = getMockedTemplate();
+        when(templateDao.findById(10L)).thenReturn(template);
+        when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.ROOT))
+                .thenReturn(Arrays.asList(Mockito.mock(VolumeVO.class), 
Mockito.mock(VolumeVO.class)));
+        volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, 
Mockito.mock(VolumeInfo.class));
+    }
+
+    @Test
+    public void testGetVmExistingVolumeForVolumeAttach_SingleRootVolume() {
+        UserVmVO vm = getMockedVm();
+        VMTemplateVO template = getMockedTemplate();
+        VolumeVO rootVolume = Mockito.mock(VolumeVO.class);
+        Mockito.when(rootVolume.getId()).thenReturn(20L);
+        Mockito.when(templateDao.findById(10L)).thenReturn(template);
+        Mockito.when(volumeDaoMock.findByInstanceAndType(1L, Volume.Type.ROOT))
+                .thenReturn(Collections.singletonList(rootVolume));
+        VolumeVO result = 
volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, 
Mockito.mock(VolumeInfo.class));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(20L, result.getId());
+    }
+
+    private VolumeVO getMockedDataVolume() {
+        VolumeVO volume = Mockito.mock(VolumeVO.class);
+        Mockito.when(volume.getId()).thenReturn(30L);
+        Mockito.when(volume.getState()).thenReturn(Volume.State.Ready);
+        return volume;
+    }
+
+    @Test
+    public void 
testGetVmExistingVolumeForVolumeAttach_NoRootVolume_DataDiskAvailable() {
+        UserVmVO vm = getMockedVm();
+        VMTemplateVO template = getMockedTemplate();
+        VolumeVO dataDisk = getMockedDataVolume();
+        List<VolumeVO> rootVolumes = Collections.emptyList();
+        List<VolumeVO> dataVolumes = Collections.singletonList(dataDisk);
+        Mockito.when(templateDao.findById(10L)).thenReturn(template);
+        Mockito.when(volumeDaoMock.findByInstanceAndType(1L, 
Volume.Type.ROOT)).thenReturn(rootVolumes);
+        Mockito.when(volumeDaoMock.findByInstanceAndType(1L, 
Volume.Type.DATADISK)).thenReturn(dataVolumes);
+        VolumeVO result = 
volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, 
Mockito.mock(VolumeInfo.class));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(30L, result.getId());
+    }
+
+    @Test
+    public void testGetVmExistingVolumeForVolumeAttach_NoVolumesAtAll() {
+        UserVmVO vm = getMockedVm();
+        VMTemplateVO template = getMockedTemplate();
+        Mockito.when(templateDao.findById(10L)).thenReturn(template);
+        Mockito.when(volumeDaoMock.findByInstanceAndType(1L, 
Volume.Type.ROOT)).thenReturn(Collections.emptyList());
+        Mockito.when(volumeDaoMock.findByInstanceAndType(1L, 
Volume.Type.DATADISK)).thenReturn(Collections.emptyList());
+        VolumeVO result = 
volumeApiServiceImpl.getVmExistingVolumeForVolumeAttach(vm, 
Mockito.mock(VolumeInfo.class));
+        Assert.assertNull(result);
+    }
+
+    private void mockDiskOffering() {
+        DiskOfferingVO offering = Mockito.mock(DiskOfferingVO.class);
+        Mockito.when(_diskOfferingDao.findById(1L)).thenReturn(offering);
+        Mockito.when(offering.isUseLocalStorage()).thenReturn(true);
+        Mockito.when(offering.isRecreatable()).thenReturn(false);
+    }
+
+    private DataCenterVO mockZone() {
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(_dcDao.findById(1L)).thenReturn(zone);
+        return zone;
+    }
+
+    @Test
+    public void testGetPoolForAllocatedOrUploadedVolumeForAttach_Success() {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        HostPodVO pod = Mockito.mock(HostPodVO.class);
+        DataCenterVO zone = mockZone();
+        mockDiskOffering();
+        StoragePool pool = Mockito.mock(StoragePool.class);
+        when(vm.getDataCenterId()).thenReturn(1L);
+        when(virtualMachineManager.findClusterAndHostIdForVm(vm, 
false)).thenReturn(new Pair<>(1L, 2L));
+        when(clusterDao.findById(1L)).thenReturn(cluster);
+        when(cluster.getPodId()).thenReturn(1L);
+        when(podDao.findById(1L)).thenReturn(pod);
+        when(volumeToAttach.getDiskOfferingId()).thenReturn(1L);
+        
when(volumeOrchestrationService.findStoragePool(any(DiskProfile.class), 
eq(zone), eq(pod), eq(1L), eq(2L), eq(vm), eq(Collections.emptySet())))
+                .thenReturn(pool);
+        StoragePool result = 
volumeApiServiceImpl.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach,
 vm);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(pool, result);
+    }
+
+    @Test
+    public void 
testGetPoolForAllocatedOrUploadedVolumeForAttach_NoSuitablePoolFound_ReturnsNull()
 {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        DataCenterVO zone = mockZone();
+        Pair<Long, Long> clusterHostId = new Pair<>(1L, 2L);
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        HostPodVO pod = Mockito.mock(HostPodVO.class);
+        mockDiskOffering();
+        when(vm.getDataCenterId()).thenReturn(1L);
+        when(clusterDao.findById(1L)).thenReturn(cluster);
+        when(virtualMachineManager.findClusterAndHostIdForVm(vm, 
false)).thenReturn(clusterHostId);
+        when(podDao.findById(anyLong())).thenReturn(pod);
+        when(volumeToAttach.getDiskOfferingId()).thenReturn(1L);
+        
when(volumeOrchestrationService.findStoragePool(any(DiskProfile.class), 
eq(zone), eq(pod), eq(1L), eq(2L), eq(vm), eq(Collections.emptySet())))
+                .thenReturn(null);
+        
Assert.assertNull(volumeApiServiceImpl.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach,
 vm));
+    }
+
+    @Test
+    public void 
testGetSuitablePoolForAllocatedOrUploadedVolumeForAttach_NoCluster() {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        DataCenterVO zone = mockZone();
+        HostPodVO pod = Mockito.mock(HostPodVO.class);
+        mockDiskOffering();
+        StoragePool pool = Mockito.mock(StoragePool.class);
+        when(vm.getDataCenterId()).thenReturn(1L);
+        when(vm.getPodIdToDeployIn()).thenReturn(2L);
+        when(virtualMachineManager.findClusterAndHostIdForVm(vm, 
false)).thenReturn(new Pair<>(null, 2L));
+        when(podDao.findById(2L)).thenReturn(pod);
+        when(volumeToAttach.getDiskOfferingId()).thenReturn(1L);
+        
when(volumeOrchestrationService.findStoragePool(any(DiskProfile.class), 
eq(zone), eq(pod), eq(null), eq(2L), eq(vm), eq(Collections.emptySet())))
+                .thenReturn(pool);
+        StoragePool result = 
volumeApiServiceImpl.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach,
 vm);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(pool, result);
+    }
+
+
+    @Test
+    public void 
testCreateVolumeOnSecondaryForAttachIfNeeded_VolumeNotAllocatedOrUploaded() {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Ready);
+        VolumeInfo result = 
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(
+                volumeToAttach, Mockito.mock(UserVmVO.class), null);
+        Assert.assertSame(volumeToAttach, result);
+        Mockito.verifyNoInteractions(primaryDataStoreDaoMock, 
volumeOrchestrationService);
+    }
+
+    @Test
+    public void 
testCreateVolumeOnSecondaryForAttachIfNeeded_ExistingVolumeDeterminesStoragePool()
 {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        
Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        VolumeVO existingVolume = Mockito.mock(VolumeVO.class);
+        Mockito.when(existingVolume.getState()).thenReturn(Volume.State.Ready);
+        when(existingVolume.getPoolId()).thenReturn(1L);
+        StoragePoolVO destPrimaryStorage = Mockito.mock(StoragePoolVO.class);
+        
Mockito.when(destPrimaryStorage.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        
Mockito.when(primaryDataStoreDaoMock.findById(1L)).thenReturn(destPrimaryStorage);
+        VolumeInfo newVolumeOnPrimaryStorage = Mockito.mock(VolumeInfo.class);
+        try {
+            
Mockito.when(volumeOrchestrationService.createVolumeOnPrimaryStorage(vm, 
volumeToAttach, vm.getHypervisorType(), destPrimaryStorage))
+                    .thenReturn(newVolumeOnPrimaryStorage);
+        } catch (NoTransitionException nte) {
+            Assert.fail(nte.getMessage());
+        }
+        VolumeInfo result = 
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, 
existingVolume);
+        Assert.assertSame(newVolumeOnPrimaryStorage, result);
+        Mockito.verify(primaryDataStoreDaoMock).findById(1L);
+    }
+
+    @Test
+    public void 
testCreateVolumeOnPrimaryForAttachIfNeeded_UsesGetPoolForAttach() {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        
Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Allocated);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        StoragePool destPrimaryStorage = Mockito.mock(StoragePool.class);
+        Mockito.doReturn(destPrimaryStorage).when(volumeApiServiceImpl)
+                
.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+        VolumeInfo newVolumeOnPrimaryStorage = Mockito.mock(VolumeInfo.class);
+        try {
+            
Mockito.when(volumeOrchestrationService.createVolumeOnPrimaryStorage(
+                    vm, volumeToAttach, vm.getHypervisorType(), 
destPrimaryStorage))
+                .thenReturn(newVolumeOnPrimaryStorage);
+        } catch (NoTransitionException nte) {
+            Assert.fail(nte.getMessage());
+        }
+        VolumeInfo result = 
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, 
null);
+        Assert.assertSame(newVolumeOnPrimaryStorage, result);
+        
verify(volumeApiServiceImpl).getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach,
 vm);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void 
testCreateVolumeOnPrimaryForAttachIfNeeded_UnsupportedPoolType_ThrowsException()
 {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        StoragePool destPrimaryStorage = Mockito.mock(StoragePool.class);
+        
when(destPrimaryStorage.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
+        Mockito.doReturn(destPrimaryStorage).when(volumeApiServiceImpl)
+                
.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+        
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, 
null);
+    }
+
+    @Test
+    public void 
testCreateVolumeOnSecondaryForAttachIfNeeded_CreateVolumeFails_ThrowsException()
 {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        
Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        StoragePool destPrimaryStorage = Mockito.mock(StoragePool.class);
+        
Mockito.when(destPrimaryStorage.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        Mockito.doReturn(destPrimaryStorage).when(volumeApiServiceImpl)
+                
.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+        try {
+            
Mockito.when(volumeOrchestrationService.createVolumeOnPrimaryStorage(vm, 
volumeToAttach, vm.getHypervisorType(), destPrimaryStorage))
+                    .thenThrow(new NoTransitionException("Mocked exception"));
+        } catch (NoTransitionException nte) {
+            Assert.fail(nte.getMessage());
+        }
+        CloudRuntimeException exception = 
Assert.assertThrows(CloudRuntimeException.class, () ->
+                
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, 
null)
+        );
+        Assert.assertTrue(exception.getMessage().contains("Failed to create 
volume on primary storage"));
+    }
+
+    @Test
+    public void 
testCreateVolumeOnSecondaryForAttachIfNeeded_NoSuitablePool_ThrowsException() {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        
Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Uploaded);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        Mockito.doReturn(null).when(volumeApiServiceImpl)
+                
.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+        CloudRuntimeException exception = 
Assert.assertThrows(CloudRuntimeException.class, () ->
+                
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, 
null)
+        );
+        Assert.assertTrue(exception.getMessage().startsWith("Failed to find a 
primary storage for volume"));
+    }
+
+    @Test
+    public void 
testCreateVolumeOnSecondaryForAttachIfNeeded_NoSuitablePool_ReturnSameVolumeInfo()
 {
+        VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
+        
Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Allocated);
+        UserVmVO vm = Mockito.mock(UserVmVO.class);
+        Mockito.when(vm.getState()).thenReturn(State.Stopped);
+        Mockito.doReturn(null).when(volumeApiServiceImpl)
+                
.getSuitablePoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
+        VolumeInfo result = 
volumeApiServiceImpl.createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, 
null);
+        Assert.assertSame(volumeToAttach, result);
+        try {
+            Mockito.verify(volumeOrchestrationService, 
Mockito.never()).createVolumeOnPrimaryStorage(Mockito.any(),
+                    Mockito.any(), Mockito.any(), Mockito.any());
+        } catch (NoTransitionException e) {
+            Assert.fail();
+        }
+    }
 }

Reply via email to