Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add unit tests
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
  • Loading branch information
shwstppr committed Jan 27, 2025
commit 4c1548aa8163643226f02ae4274cfb378e8e245a
25 changes: 13 additions & 12 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2407,22 +2407,23 @@ protected VolumeVO getVmExistingVolumeForVolumeAttach(UserVmVO vm, VolumeInfo vo
}
}
}
if (existingVolumeOfVm != null) {
if (existingVolumeOfVm == null) {
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(),
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;
}
if (s_logger.isTraceEnabled()) {
s_logger.trace(String.format("No existing volume found for VM (%s/%s) to attach volume %s/%s",
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(),
volumeToAttach.getName(), volumeToAttach.getUuid()));
existingVolumeOfVm.getName(), existingVolumeOfVm.getUuid(),
existingVolumeOfVm.getPoolId()));
}
return null;
return existingVolumeOfVm;
}

protected StoragePool getPoolForAllocatedOrUploadedVolumeForAttach(final VolumeInfo volumeToAttach, final UserVmVO vm) {
Expand All @@ -2448,7 +2449,7 @@ protected StoragePool getPoolForAllocatedOrUploadedVolumeForAttach(final VolumeI
return pool;
}

protected VolumeInfo createVolumeOnSecondaryForAttachIfNeeded(final VolumeInfo volumeToAttach, final UserVmVO vm, VolumeVO existingVolumeOfVm) {
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())) {
Expand All @@ -2466,7 +2467,7 @@ protected VolumeInfo createVolumeOnSecondaryForAttachIfNeeded(final VolumeInfo v
destPrimaryStorage = getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
}
try {
if (volumeOnSecondary && destPrimaryStorage.getPoolType() == Storage.StoragePoolType.PowerFlex) {
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,
Expand All @@ -2487,7 +2488,7 @@ private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long device

UserVmVO vm = _userVmDao.findById(vmId);
VolumeVO existingVolumeOfVm = getVmExistingVolumeForVolumeAttach(vm, volumeToAttach);
VolumeInfo newVolumeOnPrimaryStorage = createVolumeOnSecondaryForAttachIfNeeded(volumeToAttach, vm, existingVolumeOfVm);
VolumeInfo newVolumeOnPrimaryStorage = createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, existingVolumeOfVm);

// reload the volume from db
newVolumeOnPrimaryStorage = volFactory.getVolume(newVolumeOnPrimaryStorage.getId());
Expand Down
249 changes: 249 additions & 0 deletions server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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;
Expand Down Expand Up @@ -86,8 +87,12 @@
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;
Expand Down Expand Up @@ -122,10 +127,12 @@
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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -1820,4 +1836,237 @@ public void testValidationsForCheckVolumeAPIWithInvalidVolumeFormat() {

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.getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
Assert.assertNotNull(result);
Assert.assertEquals(pool, result);
}

@Test(expected = CloudRuntimeException.class)
public void testGetPoolForAllocatedOrUploadedVolumeForAttach_NoPoolFound_ThrowsException() {
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);
volumeApiServiceImpl.getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
}

@Test
public void testGetPoolForAllocatedOrUploadedVolumeForAttach_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.getPoolForAllocatedOrUploadedVolumeForAttach(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)
.getPoolForAllocatedOrUploadedVolumeForAttach(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).getPoolForAllocatedOrUploadedVolumeForAttach(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)
.getPoolForAllocatedOrUploadedVolumeForAttach(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)
.getPoolForAllocatedOrUploadedVolumeForAttach(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"));
}
}