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();
+ }
+ }
}