mirror of https://github.com/apache/cloudstack.git
add more tests
This commit is contained in:
parent
e45a43db29
commit
381dc67ef0
|
|
@ -369,4 +369,286 @@ public class StorageSystemDataMotionStrategyTest {
|
|||
|
||||
assertFalse(strategy.isStoragePoolTypeInList(StoragePoolType.SharedMountPoint, listTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateMigrateDiskInfoForBlockDevice with CLVM destination pool
|
||||
* Should set driver type to RAW for CLVM
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateMigrateDiskInfoForBlockDevice_ClvmDestination() {
|
||||
MigrateCommand.MigrateDiskInfo originalDiskInfo = new MigrateCommand.MigrateDiskInfo(
|
||||
"serial123",
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
"/source/path",
|
||||
null
|
||||
);
|
||||
|
||||
StoragePoolVO destStoragePool = new StoragePoolVO();
|
||||
destStoragePool.setPoolType(StoragePoolType.CLVM);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo updatedDiskInfo = strategy.updateMigrateDiskInfoForBlockDevice(
|
||||
originalDiskInfo, destStoragePool);
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, updatedDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, updatedDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, updatedDiskInfo.getSource());
|
||||
Assert.assertEquals("serial123", updatedDiskInfo.getSerialNumber());
|
||||
Assert.assertEquals("/source/path", updatedDiskInfo.getSourceText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateMigrateDiskInfoForBlockDevice with CLVM_NG destination pool
|
||||
* Should set driver type to QCOW2 for CLVM_NG
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateMigrateDiskInfoForBlockDevice_ClvmNgDestination() {
|
||||
MigrateCommand.MigrateDiskInfo originalDiskInfo = new MigrateCommand.MigrateDiskInfo(
|
||||
"serial456",
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.RAW,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
"/source/path",
|
||||
"/backing/path"
|
||||
);
|
||||
|
||||
StoragePoolVO destStoragePool = new StoragePoolVO();
|
||||
destStoragePool.setPoolType(StoragePoolType.CLVM_NG);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo updatedDiskInfo = strategy.updateMigrateDiskInfoForBlockDevice(
|
||||
originalDiskInfo, destStoragePool);
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, updatedDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, updatedDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, updatedDiskInfo.getSource());
|
||||
Assert.assertEquals("serial456", updatedDiskInfo.getSerialNumber());
|
||||
Assert.assertEquals("/source/path", updatedDiskInfo.getSourceText());
|
||||
Assert.assertEquals("/backing/path", updatedDiskInfo.getBackingStoreText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateMigrateDiskInfoForBlockDevice with non-CLVM destination pool
|
||||
* Should return original DiskInfo unchanged
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateMigrateDiskInfoForBlockDevice_NonClvmDestination() {
|
||||
MigrateCommand.MigrateDiskInfo originalDiskInfo = new MigrateCommand.MigrateDiskInfo(
|
||||
"serial789",
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
"/source/path",
|
||||
null
|
||||
);
|
||||
|
||||
StoragePoolVO destStoragePool = new StoragePoolVO();
|
||||
destStoragePool.setPoolType(StoragePoolType.NetworkFilesystem);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo updatedDiskInfo = strategy.updateMigrateDiskInfoForBlockDevice(
|
||||
originalDiskInfo, destStoragePool);
|
||||
|
||||
Assert.assertSame(originalDiskInfo, updatedDiskInfo);
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, updatedDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, updatedDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, updatedDiskInfo.getSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test supportStoragePoolType with CLVM and CLVM_NG types
|
||||
*/
|
||||
@Test
|
||||
public void testSupportStoragePoolType_ClvmTypes() {
|
||||
assertTrue(strategy.supportStoragePoolType(StoragePoolType.CLVM, StoragePoolType.CLVM, StoragePoolType.CLVM_NG));
|
||||
assertTrue(strategy.supportStoragePoolType(StoragePoolType.CLVM_NG, StoragePoolType.CLVM, StoragePoolType.CLVM_NG));
|
||||
|
||||
assertFalse(strategy.supportStoragePoolType(StoragePoolType.CLVM));
|
||||
assertFalse(strategy.supportStoragePoolType(StoragePoolType.CLVM_NG));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test configureMigrateDiskInfo with CLVM destination
|
||||
*/
|
||||
@Test
|
||||
public void testConfigureMigrateDiskInfo_ForClvm() {
|
||||
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn("/dev/vg/volume-path").when(srcVolumeInfo).getPath();
|
||||
|
||||
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(
|
||||
srcVolumeInfo, "/dev/vg/dest-path", null);
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
|
||||
Assert.assertEquals("/dev/vg/dest-path", migrateDiskInfo.getSourceText());
|
||||
Assert.assertEquals("/dev/vg/volume-path", migrateDiskInfo.getSerialNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test configureMigrateDiskInfo with CLVM_NG destination and backing file
|
||||
*/
|
||||
@Test
|
||||
public void testConfigureMigrateDiskInfo_ForClvmNgWithBacking() {
|
||||
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn("/dev/vg/volume-path").when(srcVolumeInfo).getPath();
|
||||
|
||||
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(
|
||||
srcVolumeInfo, "/dev/vg/dest-path", "/dev/vg/backing-template");
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
|
||||
Assert.assertEquals("/dev/vg/dest-path", migrateDiskInfo.getSourceText());
|
||||
Assert.assertEquals("/dev/vg/backing-template", migrateDiskInfo.getBackingStoreText());
|
||||
Assert.assertEquals("/dev/vg/volume-path", migrateDiskInfo.getSerialNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test isStoragePoolTypeInList with CLVM types
|
||||
*/
|
||||
@Test
|
||||
public void testIsStoragePoolTypeInList_WithClvmTypes() {
|
||||
StoragePoolType[] clvmTypes = new StoragePoolType[] {
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.Filesystem
|
||||
};
|
||||
|
||||
assertTrue(strategy.isStoragePoolTypeInList(StoragePoolType.CLVM, clvmTypes));
|
||||
assertTrue(strategy.isStoragePoolTypeInList(StoragePoolType.CLVM_NG, clvmTypes));
|
||||
assertTrue(strategy.isStoragePoolTypeInList(StoragePoolType.Filesystem, clvmTypes));
|
||||
assertFalse(strategy.isStoragePoolTypeInList(StoragePoolType.NetworkFilesystem, clvmTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test supportStoragePoolType with mixed CLVM and NFS types
|
||||
*/
|
||||
@Test
|
||||
public void testSupportStoragePoolType_MixedClvmAndNfs() {
|
||||
assertTrue(strategy.supportStoragePoolType(
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.NetworkFilesystem
|
||||
));
|
||||
|
||||
assertTrue(strategy.supportStoragePoolType(
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.NetworkFilesystem
|
||||
));
|
||||
|
||||
assertTrue(strategy.supportStoragePoolType(
|
||||
StoragePoolType.NetworkFilesystem,
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with CLVM source and managed destination
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmSourceManagedDestination() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with CLVM_NG source and managed destination
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmNgSourceManagedDestination() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM_NG).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with both CLVM source and CLVM_NG destination
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmToClvmNg() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
StoragePoolVO destPool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM_NG).when(destPool).getPoolType();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with CLVM_NG to CLVM migration
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmNgToClvm() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM_NG).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
StoragePoolVO destPool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM).when(destPool).getPoolType();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
|
|
@ -39,6 +40,10 @@ import com.cloud.storage.Storage;
|
|||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.vm.UserVmVO;
|
||||
import com.cloud.vm.VirtualMachine.State;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.snapshot.VMSnapshot;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultVMSnapshotStrategyTest {
|
||||
|
|
@ -46,6 +51,8 @@ public class DefaultVMSnapshotStrategyTest {
|
|||
VolumeDao volumeDao;
|
||||
@Mock
|
||||
PrimaryDataStoreDao primaryDataStoreDao;
|
||||
@Mock
|
||||
UserVmDao userVmDao;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
|
|
@ -85,7 +92,7 @@ public class DefaultVMSnapshotStrategyTest {
|
|||
Mockito.when(vol2.getChainInfo()).thenReturn(newVolChain);
|
||||
Mockito.when(vol2.getSize()).thenReturn(vmSnapshotChainSize);
|
||||
Mockito.when(vol2.getId()).thenReturn(volumeId);
|
||||
VolumeVO volumeVO = new VolumeVO("name", 0l, 0l, 0l, 0l, 0l, "folder", "path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT);
|
||||
VolumeVO volumeVO = new VolumeVO("name", 0L, 0L, 0L, 0L, 0L, "folder", "path", Storage.ProvisioningType.THIN, 0L, Volume.Type.ROOT);
|
||||
volumeVO.setPoolId(oldPoolId);
|
||||
volumeVO.setChainInfo(oldVolChain);
|
||||
volumeVO.setPath(oldVolPath);
|
||||
|
|
@ -103,4 +110,110 @@ public class DefaultVMSnapshotStrategyTest {
|
|||
Assert.assertEquals(vmSnapshotChainSize, persistedVolume.getVmSnapshotChainSize());
|
||||
Assert.assertEquals(newVolChain, persistedVolume.getChainInfo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanHandleRunningVMOnClvmStorageCantHandle() {
|
||||
Long vmId = 1L;
|
||||
VMSnapshot vmSnapshot = Mockito.mock(VMSnapshot.class);
|
||||
Mockito.when(vmSnapshot.getVmId()).thenReturn(vmId);
|
||||
|
||||
UserVmVO vm = Mockito.mock(UserVmVO.class);
|
||||
Mockito.when(vm.getId()).thenReturn(vmId);
|
||||
Mockito.when(vm.getState()).thenReturn(State.Running);
|
||||
Mockito.when(userVmDao.findById(vmId)).thenReturn(vm);
|
||||
|
||||
VolumeVO volumeOnClvm = createVolume(vmId, 1L);
|
||||
List<VolumeVO> volumes = List.of(volumeOnClvm);
|
||||
Mockito.when(volumeDao.findByInstance(vmId)).thenReturn(volumes);
|
||||
|
||||
StoragePoolVO clvmPool = createStoragePool("clvm-pool", Storage.StoragePoolType.CLVM);
|
||||
Mockito.when(primaryDataStoreDao.findById(1L)).thenReturn(clvmPool);
|
||||
|
||||
StrategyPriority result = defaultVMSnapshotStrategy.canHandle(vmSnapshot);
|
||||
|
||||
Assert.assertEquals("Should return CANT_HANDLE for running VM on CLVM storage",
|
||||
StrategyPriority.CANT_HANDLE, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanHandleStoppedVMOnClvmStorageCanHandle() {
|
||||
Long vmId = 1L;
|
||||
VMSnapshot vmSnapshot = Mockito.mock(VMSnapshot.class);
|
||||
Mockito.when(vmSnapshot.getVmId()).thenReturn(vmId);
|
||||
|
||||
UserVmVO vm = Mockito.mock(UserVmVO.class);
|
||||
Mockito.when(vm.getId()).thenReturn(vmId);
|
||||
Mockito.when(vm.getState()).thenReturn(State.Stopped);
|
||||
Mockito.when(userVmDao.findById(vmId)).thenReturn(vm);
|
||||
|
||||
StrategyPriority result = defaultVMSnapshotStrategy.canHandle(vmSnapshot);
|
||||
Assert.assertEquals("Should return DEFAULT for stopped VM on CLVM storage",
|
||||
StrategyPriority.DEFAULT, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanHandleRunningVMOnNfsStorageCanHandle() {
|
||||
Long vmId = 1L;
|
||||
VMSnapshot vmSnapshot = Mockito.mock(VMSnapshot.class);
|
||||
Mockito.when(vmSnapshot.getVmId()).thenReturn(vmId);
|
||||
|
||||
UserVmVO vm = Mockito.mock(UserVmVO.class);
|
||||
Mockito.when(vm.getId()).thenReturn(vmId);
|
||||
Mockito.when(vm.getState()).thenReturn(State.Running);
|
||||
Mockito.when(userVmDao.findById(vmId)).thenReturn(vm);
|
||||
|
||||
VolumeVO volumeOnNfs = createVolume(vmId, 1L);
|
||||
List<VolumeVO> volumes = List.of(volumeOnNfs);
|
||||
Mockito.when(volumeDao.findByInstance(vmId)).thenReturn(volumes);
|
||||
|
||||
StoragePoolVO nfsPool = createStoragePool("nfs-pool", Storage.StoragePoolType.NetworkFilesystem);
|
||||
Mockito.when(primaryDataStoreDao.findById(1L)).thenReturn(nfsPool);
|
||||
|
||||
StrategyPriority result = defaultVMSnapshotStrategy.canHandle(vmSnapshot);
|
||||
|
||||
Assert.assertEquals("Should return DEFAULT for running VM on NFS storage",
|
||||
StrategyPriority.DEFAULT, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanHandleRunningVMWithMixedStorageClvmAndNfsCantHandle() {
|
||||
// Arrange - VM has volumes on both CLVM and NFS
|
||||
Long vmId = 1L;
|
||||
VMSnapshot vmSnapshot = Mockito.mock(VMSnapshot.class);
|
||||
Mockito.when(vmSnapshot.getVmId()).thenReturn(vmId);
|
||||
|
||||
UserVmVO vm = Mockito.mock(UserVmVO.class);
|
||||
Mockito.when(vm.getId()).thenReturn(vmId);
|
||||
Mockito.when(vm.getState()).thenReturn(State.Running);
|
||||
Mockito.when(userVmDao.findById(vmId)).thenReturn(vm);
|
||||
|
||||
VolumeVO volumeOnClvm = createVolume(vmId, 1L);
|
||||
VolumeVO volumeOnNfs = createVolume(vmId, 2L);
|
||||
List<VolumeVO> volumes = List.of(volumeOnClvm, volumeOnNfs);
|
||||
Mockito.when(volumeDao.findByInstance(vmId)).thenReturn(volumes);
|
||||
|
||||
StoragePoolVO clvmPool = createStoragePool("clvm-pool", Storage.StoragePoolType.CLVM);
|
||||
StoragePoolVO nfsPool = createStoragePool("nfs-pool", Storage.StoragePoolType.NetworkFilesystem);
|
||||
Mockito.when(primaryDataStoreDao.findById(1L)).thenReturn(clvmPool);
|
||||
|
||||
StrategyPriority result = defaultVMSnapshotStrategy.canHandle(vmSnapshot);
|
||||
|
||||
Assert.assertEquals("Should return CANT_HANDLE if any volume is on CLVM storage for running VM",
|
||||
StrategyPriority.CANT_HANDLE, result);
|
||||
}
|
||||
|
||||
private VolumeVO createVolume(Long vmId, Long poolId) {
|
||||
VolumeVO volume = new VolumeVO("volume", 0L, 0L, 0L, 0L, 0L,
|
||||
"folder", "path", Storage.ProvisioningType.THIN, 0L, Volume.Type.ROOT);
|
||||
volume.setInstanceId(vmId);
|
||||
volume.setPoolId(poolId);
|
||||
return volume;
|
||||
}
|
||||
|
||||
private StoragePoolVO createStoragePool(String name, Storage.StoragePoolType poolType) {
|
||||
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(pool.getName()).thenReturn(name);
|
||||
Mockito.when(pool.getPoolType()).thenReturn(poolType);
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7209,4 +7209,305 @@ public class LibvirtComputingResourceTest {
|
|||
libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, null);
|
||||
Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE_DATA, DiskDef.DiskFmtType.QCOW2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_ValidPath() {
|
||||
String devicePath = "/dev/vg1/volume-123";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("vg1", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_ComplexVGName() {
|
||||
String devicePath = "/dev/cloudstack-vg-primary/volume-456";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("cloudstack-vg-primary", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_MultiLevelPath() {
|
||||
String devicePath = "/dev/vg-cluster-01/lv-data-001";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("vg-cluster-01", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_NullPath() {
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(null);
|
||||
assertNull(vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_EmptyPath() {
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath("");
|
||||
assertNull(vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_NonDevPath() {
|
||||
String devicePath = "/var/lib/libvirt/images/disk.qcow2";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertNull(vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_InvalidFormat() {
|
||||
String devicePath = "/dev/";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertNull(vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_OnlyVG() {
|
||||
String devicePath = "/dev/vg1";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
// Implementation extracts parts[2] regardless of whether there's an LV name
|
||||
assertEquals("vg1", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_MapperPath() {
|
||||
String devicePath = "/dev/mapper/vg1-volume";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("mapper", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_WithDashes() {
|
||||
String devicePath = "/dev/vg-name-with-dashes/lv-name";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("vg-name-with-dashes", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_WithUnderscores() {
|
||||
String devicePath = "/dev/vg_name_with_underscores/lv_name";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("vg_name_with_underscores", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckIfVolumeGroupIsClustered_NullVGName() {
|
||||
boolean result = LibvirtComputingResource.checkIfVolumeGroupIsClustered(null);
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckIfVolumeGroupIsClustered_EmptyVGName() {
|
||||
boolean result = LibvirtComputingResource.checkIfVolumeGroupIsClustered("");
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateClvmVolumeExclusive_ValidPath() {
|
||||
try {
|
||||
String volumePath = "/dev/test-vg/test-lv";
|
||||
LibvirtComputingResource.activateClvmVolumeExclusive(volumePath);
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage().toLowerCase();
|
||||
assertTrue("Should be LVM-related error",
|
||||
message.contains("lvm") ||
|
||||
message.contains("lvchange") ||
|
||||
message.contains("volume") ||
|
||||
message.contains("not found") ||
|
||||
message.contains("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivateClvmVolume_ValidPath() {
|
||||
String volumePath = "/dev/test-vg/test-lv";
|
||||
|
||||
LibvirtComputingResource.deactivateClvmVolume(volumePath);
|
||||
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetClvmVolumeToSharedMode_ValidPath() {
|
||||
String volumePath = "/dev/test-vg/test-lv";
|
||||
|
||||
LibvirtComputingResource.setClvmVolumeToSharedMode(volumePath);
|
||||
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivateClvmVolume_NullPath() {
|
||||
LibvirtComputingResource.deactivateClvmVolume(null);
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetClvmVolumeToSharedMode_NullPath() {
|
||||
LibvirtComputingResource.setClvmVolumeToSharedMode(null);
|
||||
assertTrue(true); // Passes if no exception
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivateClvmVolume_EmptyPath() {
|
||||
LibvirtComputingResource.deactivateClvmVolume("");
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetClvmVolumeToSharedMode_EmptyPath() {
|
||||
LibvirtComputingResource.setClvmVolumeToSharedMode("");
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivateClvmVolume_InvalidPath() {
|
||||
String invalidPath = "/invalid/path/that/does/not/exist";
|
||||
LibvirtComputingResource.deactivateClvmVolume(invalidPath);
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetClvmVolumeToSharedMode_InvalidPath() {
|
||||
// Should handle invalid path gracefully without throwing
|
||||
String invalidPath = "/invalid/path/that/does/not/exist";
|
||||
LibvirtComputingResource.setClvmVolumeToSharedMode(invalidPath);
|
||||
assertTrue(true); // Passes if no exception
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_RealWorldPaths() {
|
||||
assertEquals("acsvg", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/acsvg/volume-123"));
|
||||
assertEquals("cloudstack-primary", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/cloudstack-primary/vm-disk-1"));
|
||||
assertEquals("ceph-vg", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/ceph-vg/snapshot-456"));
|
||||
assertEquals("vg01", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/vg01/data"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckIfVolumeGroupIsClustered_NonExistentVG() {
|
||||
String nonExistentVG = "non-existent-vg-" + System.currentTimeMillis();
|
||||
boolean result = LibvirtComputingResource.checkIfVolumeGroupIsClustered(nonExistentVG);
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateClvmVolumeExclusive_ComplexPath() {
|
||||
try {
|
||||
String complexPath = "/dev/cloudstack-vg-primary-cluster-01/volume-123-456-789-abc";
|
||||
LibvirtComputingResource.activateClvmVolumeExclusive(complexPath);
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage().toLowerCase();
|
||||
assertTrue("Should be LVM-related error",
|
||||
message.contains("lvm") ||
|
||||
message.contains("lvchange") ||
|
||||
message.contains("volume") ||
|
||||
message.contains("not found") ||
|
||||
message.contains("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeactivateClvmVolume_ComplexPath() {
|
||||
String complexPath = "/dev/cloudstack-vg-primary-cluster-01/volume-123-456-789-abc";
|
||||
LibvirtComputingResource.deactivateClvmVolume(complexPath);
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_SpecialCharacters() {
|
||||
assertEquals("vg.name", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/vg.name/lv"));
|
||||
assertEquals("vg_name", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/vg_name/lv"));
|
||||
assertEquals("vg-name", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/vg-name/lv"));
|
||||
assertEquals("vg123", LibvirtComputingResource.extractVolumeGroupFromPath("/dev/vg123/lv456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_TrailingSlash() {
|
||||
String devicePath = "/dev/vg1/volume-123/";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("vg1", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckIfVolumeGroupIsClustered_WhitespaceVGName() {
|
||||
boolean result = LibvirtComputingResource.checkIfVolumeGroupIsClustered(" ");
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_DevMapperExcluded() {
|
||||
String mapperPath1 = "/dev/mapper/vg1-lv1";
|
||||
String mapperPath2 = "/dev/mapper/cloudstack--vg-volume--1";
|
||||
|
||||
assertEquals("mapper", LibvirtComputingResource.extractVolumeGroupFromPath(mapperPath1));
|
||||
assertEquals("mapper", LibvirtComputingResource.extractVolumeGroupFromPath(mapperPath2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_EdgeCases() {
|
||||
assertNull(LibvirtComputingResource.extractVolumeGroupFromPath("/dev"));
|
||||
assertNull(LibvirtComputingResource.extractVolumeGroupFromPath("/dev/"));
|
||||
assertNull(LibvirtComputingResource.extractVolumeGroupFromPath("dev/vg/lv"));
|
||||
assertNull(LibvirtComputingResource.extractVolumeGroupFromPath("//dev//vg//lv"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClvmVolumeActivationSequence() {
|
||||
// Test a typical sequence: deactivate -> activate exclusive -> deactivate -> shared
|
||||
String volumePath = "/dev/test-vg/test-volume";
|
||||
|
||||
LibvirtComputingResource.deactivateClvmVolume(volumePath);
|
||||
|
||||
try {
|
||||
LibvirtComputingResource.activateClvmVolumeExclusive(volumePath);
|
||||
} catch (Exception e) {
|
||||
// Expected in test environment
|
||||
}
|
||||
|
||||
LibvirtComputingResource.deactivateClvmVolume(volumePath);
|
||||
LibvirtComputingResource.setClvmVolumeToSharedMode(volumePath);
|
||||
|
||||
assertTrue(true); // Test passes if sequence completes
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_LongVGName() {
|
||||
String longVGName = "a".repeat(100);
|
||||
String devicePath = "/dev/" + longVGName + "/volume";
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals(longVGName, vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVolumeGroupFromPath_LongLVName() {
|
||||
String longLVName = "volume-" + "b".repeat(100);
|
||||
String devicePath = "/dev/vg1/" + longLVName;
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(devicePath);
|
||||
assertEquals("vg1", vgName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckIfVolumeGroupIsClustered_SpecialCharactersInName() {
|
||||
assertFalse(LibvirtComputingResource.checkIfVolumeGroupIsClustered("vg.test.name"));
|
||||
assertFalse(LibvirtComputingResource.checkIfVolumeGroupIsClustered("vg_test_name"));
|
||||
assertFalse(LibvirtComputingResource.checkIfVolumeGroupIsClustered("vg-test-name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClvmMethodsWithMultiplePaths() {
|
||||
String[] paths = {
|
||||
"/dev/vg1/vol1",
|
||||
"/dev/vg2/vol2",
|
||||
"/dev/cloudstack-primary/vol3",
|
||||
"/dev/test-vg/test-vol"
|
||||
};
|
||||
|
||||
for (String path : paths) {
|
||||
LibvirtComputingResource.deactivateClvmVolume(path);
|
||||
LibvirtComputingResource.setClvmVolumeToSharedMode(path);
|
||||
|
||||
String vgName = LibvirtComputingResource.extractVolumeGroupFromPath(path);
|
||||
assertNotNull("Should extract VG from: " + path, vgName);
|
||||
|
||||
boolean clustered = LibvirtComputingResource.checkIfVolumeGroupIsClustered(vgName);
|
||||
}
|
||||
|
||||
assertTrue(true); // Passes if all paths processed
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,468 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.cloudstack.storage.command.ClvmLockTransferCommand;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedConstruction;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
/**
|
||||
* Tests for LibvirtClvmLockTransferCommandWrapper
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LibvirtClvmLockTransferCommandWrapperTest {
|
||||
|
||||
@Mock
|
||||
private LibvirtComputingResource libvirtComputingResource;
|
||||
|
||||
@Mock
|
||||
private Logger logger;
|
||||
|
||||
private LibvirtClvmLockTransferCommandWrapper wrapper;
|
||||
|
||||
private static final String TEST_LV_PATH = "/dev/vg1/volume-123";
|
||||
private static final String TEST_VOLUME_UUID = "test-volume-uuid-456";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
wrapper = new LibvirtClvmLockTransferCommandWrapper();
|
||||
wrapper.logger = logger;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_DeactivateSuccess() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null); // Success
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertTrue(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("deactivated"));
|
||||
assertTrue(answer.getDetails().contains(TEST_VOLUME_UUID));
|
||||
|
||||
// Verify script was constructed with correct parameters
|
||||
assertEquals(1, scriptMock.constructed().size());
|
||||
Script script = scriptMock.constructed().get(0);
|
||||
verify(script).add("-an");
|
||||
verify(script).add(TEST_LV_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ActivateExclusiveSuccess() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null); // Success
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertTrue(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("activated exclusively"));
|
||||
assertTrue(answer.getDetails().contains(TEST_VOLUME_UUID));
|
||||
|
||||
// Verify script was constructed with correct parameters
|
||||
assertEquals(1, scriptMock.constructed().size());
|
||||
Script script = scriptMock.constructed().get(0);
|
||||
verify(script).add("-aey");
|
||||
verify(script).add(TEST_LV_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ActivateSharedSuccess() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_SHARED,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null); // Success
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertTrue(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("activated in shared mode"));
|
||||
assertTrue(answer.getDetails().contains(TEST_VOLUME_UUID));
|
||||
|
||||
// Verify script was constructed with correct parameters
|
||||
assertEquals(1, scriptMock.constructed().size());
|
||||
Script script = scriptMock.constructed().get(0);
|
||||
verify(script).add("-asy");
|
||||
verify(script).add(TEST_LV_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_LvchangeFailure() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
String errorMessage = "lvchange: Volume is in use";
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(errorMessage);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("lvchange -an"));
|
||||
assertTrue(answer.getDetails().contains(TEST_LV_PATH));
|
||||
assertTrue(answer.getDetails().contains(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ScriptTimeout() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
String timeoutMessage = "Script timed out after 30000ms";
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(timeoutMessage);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("failed"));
|
||||
assertTrue(answer.getDetails().contains(timeoutMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_NullLvPath() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
null,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
// Should still execute, but may fail or succeed depending on lvchange behavior
|
||||
// At minimum, it should handle null gracefully
|
||||
assertEquals(1, scriptMock.constructed().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_EmptyLvPath() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
"",
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn("lvchange: Please specify a logical volume path");
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_InvalidLvPath() {
|
||||
String invalidPath = "/invalid/path/that/does/not/exist";
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE,
|
||||
invalidPath,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
String errorMessage = "Failed to find logical volume \"" + invalidPath + "\"";
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(errorMessage);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("failed"));
|
||||
assertTrue(answer.getDetails().contains(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ExceptionDuringExecution() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
RuntimeException testException = new RuntimeException("Test exception");
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenThrow(testException);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains("Exception"));
|
||||
assertTrue(answer.getDetails().contains("Test exception"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_VerifyScriptConstruction() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
// Just set up the mock behavior - don't assert here as it can interfere
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
// Verify the answer is successful
|
||||
assertNotNull("Answer should not be null", answer);
|
||||
assertTrue("Answer should indicate success. Details: " + answer.getDetails(),
|
||||
answer.getResult());
|
||||
|
||||
// Verify that Script was constructed exactly once
|
||||
assertEquals("Script should be constructed once", 1, scriptMock.constructed().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_AllOperationsUseDifferentFlags() {
|
||||
// Test that each operation uses the correct lvchange flag
|
||||
|
||||
// DEACTIVATE -> -an
|
||||
ClvmLockTransferCommand deactivateCmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
wrapper.execute(deactivateCmd, libvirtComputingResource);
|
||||
verify(scriptMock.constructed().get(0)).add("-an");
|
||||
}
|
||||
|
||||
// ACTIVATE_EXCLUSIVE -> -aey
|
||||
ClvmLockTransferCommand exclusiveCmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
wrapper.execute(exclusiveCmd, libvirtComputingResource);
|
||||
verify(scriptMock.constructed().get(0)).add("-aey");
|
||||
}
|
||||
|
||||
// ACTIVATE_SHARED -> -asy
|
||||
ClvmLockTransferCommand sharedCmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_SHARED,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
wrapper.execute(sharedCmd, libvirtComputingResource);
|
||||
verify(scriptMock.constructed().get(0)).add("-asy");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_LvchangeVolumeInUseError() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
String errorMessage = "Can't deactivate volume group with active logical volumes";
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(errorMessage);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_LvchangePermissionDenied() {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE,
|
||||
TEST_LV_PATH,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
String errorMessage = "Permission denied";
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(errorMessage);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertFalse(answer.getResult());
|
||||
assertTrue(answer.getDetails().contains(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ComplexLvPath() {
|
||||
String complexPath = "/dev/cloudstack-vg-primary/volume-123-456-789-abc-def";
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE,
|
||||
complexPath,
|
||||
TEST_VOLUME_UUID
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertTrue(answer.getResult());
|
||||
|
||||
// Verify the complex path was passed correctly
|
||||
Script script = scriptMock.constructed().get(0);
|
||||
verify(script).add(complexPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_SequentialOperations() {
|
||||
// Test that multiple operations can be executed sequentially
|
||||
String[] paths = {
|
||||
"/dev/vg1/vol1",
|
||||
"/dev/vg1/vol2",
|
||||
"/dev/vg2/vol3"
|
||||
};
|
||||
|
||||
for (String path : paths) {
|
||||
ClvmLockTransferCommand cmd = new ClvmLockTransferCommand(
|
||||
ClvmLockTransferCommand.Operation.DEACTIVATE,
|
||||
path,
|
||||
"uuid-" + path.hashCode()
|
||||
);
|
||||
|
||||
try (MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
})) {
|
||||
|
||||
Answer answer = wrapper.execute(cmd, libvirtComputingResource);
|
||||
|
||||
assertNotNull(answer);
|
||||
assertTrue(answer.getResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
@ -454,7 +455,7 @@ public class LibvirtStorageAdaptorTest {
|
|||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeName, size, mockPool);
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
|
@ -542,7 +543,7 @@ public class LibvirtStorageAdaptorTest {
|
|||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, timeout, virtualSize,
|
||||
null, mockPool, Storage.ProvisioningType.THIN);
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
|
@ -578,7 +579,7 @@ public class LibvirtStorageAdaptorTest {
|
|||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, timeout, virtualSize,
|
||||
null, mockPool, Storage.ProvisioningType.THIN);
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
|
@ -602,7 +603,7 @@ public class LibvirtStorageAdaptorTest {
|
|||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
|
@ -620,7 +621,7 @@ public class LibvirtStorageAdaptorTest {
|
|||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
|
@ -638,8 +639,688 @@ public class LibvirtStorageAdaptorTest {
|
|||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSecureZeroFill_EnabledInDetails() throws Exception {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "true");
|
||||
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"shouldSecureZeroFill", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
|
||||
assert result : "Should return true when CLVM_SECURE_ZERO_FILL is 'true'";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSecureZeroFill_DisabledInDetails() throws Exception {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "false");
|
||||
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"shouldSecureZeroFill", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
|
||||
assert !result : "Should return false when CLVM_SECURE_ZERO_FILL is 'false'";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSecureZeroFill_NotSetInDetails() throws Exception {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"shouldSecureZeroFill", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
|
||||
assert !result : "Should return false when CLVM_SECURE_ZERO_FILL is not set";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSecureZeroFill_NullDetails() throws Exception {
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(null);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"shouldSecureZeroFill", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
|
||||
assert !result : "Should return false when details are null";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVgNameFromPool_SimplePath() throws Exception {
|
||||
String vgName = "testvg";
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"extractVgNameFromPool", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String result = (String) method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
|
||||
assertEquals("Should extract VG name correctly", vgName, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVgNameFromPool_PathWithSlash() throws Exception {
|
||||
String vgName = "testvg";
|
||||
String path = "/" + vgName;
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(path);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"extractVgNameFromPool", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String result = (String) method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
|
||||
assertEquals("Should extract VG name from path with leading slash", vgName, result);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testExtractVgNameFromPool_NullPath() throws Throwable {
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(null);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"extractVgNameFromPool", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLvExists_VolumeExists() throws Exception {
|
||||
String lvPath = "/dev/testvg/test-volume";
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn(null);
|
||||
});
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"lvExists", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, lvPath);
|
||||
|
||||
assert result : "Should return true when LV exists (lvs returns null)";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLvExists_VolumeDoesNotExist() throws Exception {
|
||||
String lvPath = "/dev/testvg/nonexistent-volume";
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> {
|
||||
when(mock.execute()).thenReturn("Volume not found");
|
||||
});
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"lvExists", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, lvPath);
|
||||
|
||||
assert !result : "Should return false when LV does not exist";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVgName_SimpleName() throws Exception {
|
||||
String vgName = "acsvg";
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"getVgName", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String result = (String) method.invoke(libvirtStorageAdaptor, vgName);
|
||||
|
||||
assertEquals("Should return VG name as-is for simple name", vgName, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVgName_PathWithSlash() throws Exception {
|
||||
String vgName = "acsvg";
|
||||
String path = "/" + vgName;
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"getVgName", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String result = (String) method.invoke(libvirtStorageAdaptor, path);
|
||||
|
||||
assertEquals("Should extract VG name from path with leading slash", vgName, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVgName_DevPath() throws Exception {
|
||||
String vgName = "acsvg";
|
||||
String path = "/dev/" + vgName;
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"getVgName", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String result = (String) method.invoke(libvirtStorageAdaptor, path);
|
||||
|
||||
assertEquals("Should extract VG name from /dev/ path", vgName, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePhysicalDiskFromClvmLv_CLVM() throws Exception {
|
||||
String lvPath = "/dev/testvg/test-volume";
|
||||
String volumeUuid = "test-volume";
|
||||
long size = 10737418240L;
|
||||
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"createPhysicalDiskFromClvmLv",
|
||||
String.class, String.class, KVMStoragePool.class, long.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
KVMPhysicalDisk result = (KVMPhysicalDisk) method.invoke(
|
||||
libvirtStorageAdaptor, lvPath, volumeUuid, mockPool, size);
|
||||
|
||||
assertNotNull("Physical disk should be created", result);
|
||||
assertEquals("Format should be RAW for CLVM", PhysicalDiskFormat.RAW, result.getFormat());
|
||||
assertEquals("Size should match", size, result.getSize());
|
||||
assertEquals("Path should match", lvPath, result.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePhysicalDiskFromClvmLv_CLVM_NG() throws Exception {
|
||||
String lvPath = "/dev/testvg-ng/test-volume";
|
||||
String volumeUuid = "test-volume";
|
||||
long size = 10737418240L;
|
||||
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"createPhysicalDiskFromClvmLv",
|
||||
String.class, String.class, KVMStoragePool.class, long.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
KVMPhysicalDisk result = (KVMPhysicalDisk) method.invoke(
|
||||
libvirtStorageAdaptor, lvPath, volumeUuid, mockPool, size);
|
||||
|
||||
assertNotNull("Physical disk should be created", result);
|
||||
assertEquals("Format should be QCOW2 for CLVM_NG", PhysicalDiskFormat.QCOW2, result.getFormat());
|
||||
assertEquals("Size should match", size, result.getSize());
|
||||
assertEquals("Path should match", lvPath, result.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolSecureZeroFill_TrueValue() {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "true");
|
||||
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
String value = mockPool.getDetails().get(KVMStoragePool.CLVM_SECURE_ZERO_FILL);
|
||||
assert "true".equals(value) : "CLVM_SECURE_ZERO_FILL should be 'true'";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolSecureZeroFill_FalseValue() {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "false");
|
||||
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
String value = mockPool.getDetails().get(KVMStoragePool.CLVM_SECURE_ZERO_FILL);
|
||||
assert "false".equals(value) : "CLVM_SECURE_ZERO_FILL should be 'false'";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMVolumePathConstruction() {
|
||||
String vgName = "acsvg";
|
||||
String volumeUuid = "550e8400-e29b-41d4-a716-446655440000";
|
||||
String expectedPath = "/dev/" + vgName + "/" + volumeUuid;
|
||||
|
||||
assertEquals("/dev/acsvg/550e8400-e29b-41d4-a716-446655440000", expectedPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMNGPoolSupportsQCOW2Format() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
Storage.StoragePoolType type = mockPool.getType();
|
||||
PhysicalDiskFormat expectedFormat = PhysicalDiskFormat.QCOW2;
|
||||
|
||||
assert type == Storage.StoragePoolType.CLVM_NG : "Pool type should be CLVM_NG";
|
||||
assertNotNull("QCOW2 format should be available", expectedFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolSupportsRAWFormat() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
Storage.StoragePoolType type = mockPool.getType();
|
||||
PhysicalDiskFormat expectedFormat = PhysicalDiskFormat.RAW;
|
||||
|
||||
assert type == Storage.StoragePoolType.CLVM : "Pool type should be CLVM";
|
||||
assertNotNull("RAW format should be available", expectedFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyLvExistsInVg_VolumeExists() throws Exception {
|
||||
String volumeUuid = "test-volume-uuid";
|
||||
String vgName = "testvg";
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn(null));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"verifyLvExistsInVg", String.class, String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, vgName);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyLvExistsInVg_VolumeDoesNotExist() throws Throwable {
|
||||
String volumeUuid = "nonexistent-volume";
|
||||
String vgName = "testvg";
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn(" Volume not found"));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"verifyLvExistsInVg", String.class, String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, vgName);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetClvmVolumeSize_MethodExists() throws Exception {
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"getClvmVolumeSize", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
assertNotNull("getClvmVolumeSize method should exist", method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolIsBlockBased() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
boolean isBlockBased = mockPool.getType() == Storage.StoragePoolType.CLVM ||
|
||||
mockPool.getType() == Storage.StoragePoolType.CLVM_NG;
|
||||
|
||||
assert isBlockBased : "CLVM should be recognized as block-based storage";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMNGPoolIsBlockBased() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
boolean isBlockBased = mockPool.getType() == Storage.StoragePoolType.CLVM ||
|
||||
mockPool.getType() == Storage.StoragePoolType.CLVM_NG;
|
||||
|
||||
assert isBlockBased : "CLVM_NG should be recognized as block-based storage";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVgName_ComplexPath() throws Exception {
|
||||
String vgName = "acsvg-ng";
|
||||
String path = "/dev/mapper/" + vgName;
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"getVgName", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
String result = (String) method.invoke(libvirtStorageAdaptor, path);
|
||||
|
||||
assertNotNull("VG name should not be null", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractVgNameFromPool_EmptyPath() throws Exception {
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn("");
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"extractVgNameFromPool", KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
try {
|
||||
method.invoke(libvirtStorageAdaptor, mockPool);
|
||||
assert false : "Should throw CloudRuntimeException for empty path";
|
||||
} catch (InvocationTargetException e) {
|
||||
assert e.getCause() instanceof CloudRuntimeException :
|
||||
"Should throw CloudRuntimeException";
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupCLVMVolume_VolumeExists_WithSecureZeroFill() throws Exception {
|
||||
String volumeUuid = UUID.randomUUID().toString();
|
||||
String vgName = "testvg";
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "true");
|
||||
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
|
||||
Mockito.when(mockPool.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn(null));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"cleanupCLVMVolume", String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
|
||||
assert result : "Cleanup should succeed";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupCLVMVolume_VolumeExists_WithoutSecureZeroFill() throws Exception {
|
||||
String volumeUuid = UUID.randomUUID().toString();
|
||||
String vgName = "testvg";
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "false");
|
||||
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
|
||||
Mockito.when(mockPool.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||
Mockito.when(mockPool.getDetails()).thenReturn(details);
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn(null));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"cleanupCLVMVolume", String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
|
||||
assert result : "Cleanup should succeed without zero-fill";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupCLVMVolume_VolumeNotFound() throws Exception {
|
||||
String volumeUuid = "nonexistent-volume";
|
||||
String vgName = "testvg";
|
||||
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
|
||||
Mockito.when(mockPool.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn("Volume not found"));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"cleanupCLVMVolume", String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
|
||||
assert result : "Should return true when volume not found (already deleted)";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupCLVMVolume_NullSourceDir() throws Exception {
|
||||
String volumeUuid = UUID.randomUUID().toString();
|
||||
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(null);
|
||||
Mockito.when(mockPool.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"cleanupCLVMVolume", String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
boolean result = (boolean) method.invoke(libvirtStorageAdaptor, volumeUuid, mockPool);
|
||||
|
||||
assert result : "Should return true when source dir is null";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveLvOnFailure_Success() throws Exception {
|
||||
String lvPath = "/dev/testvg/test-volume";
|
||||
int timeout = 30000;
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn(null));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"removeLvOnFailure", String.class, int.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(libvirtStorageAdaptor, lvPath, timeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureTemplateAccessibility_CLVM_NG_TemplateVolume() throws Exception {
|
||||
String volumeUuid = "template-550e8400-e29b-41d4-a716-446655440000";
|
||||
String lvPath = "/dev/testvg/template-550e8400-e29b-41d4-a716-446655440000";
|
||||
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
mockScriptConstruction = Mockito.mockConstruction(Script.class,
|
||||
(mock, context) -> when(mock.execute()).thenReturn(null));
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"ensureTemplateAccessibility", String.class, String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, lvPath, mockPool);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsureTemplateAccessibility_CLVM_NonTemplate() throws Exception {
|
||||
String volumeUuid = UUID.randomUUID().toString();
|
||||
String lvPath = "/dev/testvg/" + volumeUuid;
|
||||
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"ensureTemplateAccessibility", String.class, String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(libvirtStorageAdaptor, volumeUuid, lvPath, mockPool);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateVirtualClvmPool_MethodExists() throws Exception {
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"createVirtualClvmPool",
|
||||
String.class, String.class, String.class,
|
||||
Storage.StoragePoolType.class, Map.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
assertNotNull("createVirtualClvmPool method should exist", method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindDeviceNodeAfterActivation_MethodExists() throws Exception {
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"findDeviceNodeAfterActivation",
|
||||
String.class, String.class, String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
assertNotNull("findDeviceNodeAfterActivation method should exist", method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMissingDeviceNode_MethodExists() throws Exception {
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"handleMissingDeviceNode",
|
||||
String.class, String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
assertNotNull("handleMissingDeviceNode method should exist", method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMissingDeviceNode_NonTemplate_MethodExists() throws Exception {
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"handleMissingDeviceNode",
|
||||
String.class, String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
assertNotNull("handleMissingDeviceNode method should exist", method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAccessibleDeviceNode_MethodExists() throws Exception {
|
||||
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
|
||||
"findAccessibleDeviceNode",
|
||||
String.class, String.class, KVMStoragePool.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
assertNotNull("findAccessibleDeviceNode method should exist", method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMNGDiskFormat_IsQCOW2() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
PhysicalDiskFormat format = (mockPool.getType() == Storage.StoragePoolType.CLVM_NG)
|
||||
? PhysicalDiskFormat.QCOW2
|
||||
: PhysicalDiskFormat.RAW;
|
||||
|
||||
assertEquals("CLVM_NG should use QCOW2 format", PhysicalDiskFormat.QCOW2, format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMDiskFormat_IsRAW() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
PhysicalDiskFormat format = (mockPool.getType() == Storage.StoragePoolType.CLVM_NG)
|
||||
? PhysicalDiskFormat.QCOW2
|
||||
: PhysicalDiskFormat.RAW;
|
||||
|
||||
assertEquals("CLVM should use RAW format", PhysicalDiskFormat.RAW, format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolDetails_DefaultSecureZeroFill() {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
|
||||
String value = details.get(KVMStoragePool.CLVM_SECURE_ZERO_FILL);
|
||||
|
||||
assert value == null : "CLVM_SECURE_ZERO_FILL should default to null";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolDetails_CustomSecureZeroFill() {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(KVMStoragePool.CLVM_SECURE_ZERO_FILL, "true");
|
||||
|
||||
String value = details.get(KVMStoragePool.CLVM_SECURE_ZERO_FILL);
|
||||
|
||||
assertEquals("CLVM_SECURE_ZERO_FILL should be 'true'", "true", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMVolumeDeviceMapperPathEscaping() {
|
||||
String vgName = "acsvg-ng";
|
||||
String volumeUuid = "ea19580e-0882-4ab8-973b-a320637037f4";
|
||||
|
||||
String vgNameEscaped = vgName.replace("-", "--");
|
||||
String volumeUuidEscaped = volumeUuid.replace("-", "--");
|
||||
String mapperPath = "/dev/mapper/" + vgNameEscaped + "-" + volumeUuidEscaped;
|
||||
|
||||
String expectedPath = "/dev/mapper/acsvg--ng-ea19580e--0882--4ab8--973b--a320637037f4";
|
||||
assertEquals("Mapper path should have escaped hyphens", expectedPath, mapperPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMTemplateNamePrefix() {
|
||||
String templateUuid = UUID.randomUUID().toString();
|
||||
String templateName = "template-" + templateUuid;
|
||||
|
||||
assert templateName.startsWith("template-");
|
||||
assert templateName.length() > 9;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolTypeIsNotFileSystem() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
boolean isFileSystem = mockPool.getType() == Storage.StoragePoolType.Filesystem;
|
||||
|
||||
assert !isFileSystem : "CLVM is not a file system pool type";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMNGPoolTypeIsNotFileSystem() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
boolean isFileSystem = mockPool.getType() == Storage.StoragePoolType.Filesystem;
|
||||
|
||||
assert !isFileSystem : "CLVM_NG is not a file system pool type";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolTypeIsNotNFS() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
boolean isNFS = mockPool.getType() == Storage.StoragePoolType.NetworkFilesystem;
|
||||
|
||||
assert !isNFS : "CLVM is not an NFS pool type";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMNGPoolTypeIsNotRBD() {
|
||||
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
|
||||
boolean isRBD = mockPool.getType() == Storage.StoragePoolType.RBD;
|
||||
|
||||
assert !isRBD : "CLVM_NG is not an RBD pool type";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMVolumeUUIDFormat() {
|
||||
String volumeUuid = UUID.randomUUID().toString();
|
||||
|
||||
assert volumeUuid.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMNGBackingFileFormat() {
|
||||
String vgName = "testvg";
|
||||
String templateUuid = UUID.randomUUID().toString();
|
||||
String backingFile = "/dev/" + vgName + "/template-" + templateUuid;
|
||||
|
||||
assert backingFile.startsWith("/dev/");
|
||||
assert backingFile.contains("template-");
|
||||
assert backingFile.contains(templateUuid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCLVMPoolLocalPathValidation() {
|
||||
String vgName = "acsvg";
|
||||
|
||||
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
|
||||
|
||||
String localPath = mockPool.getLocalPath();
|
||||
|
||||
assertNotNull("Local path should not be null", localPath);
|
||||
assert !localPath.isEmpty() : "Local path should not be empty";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue