add unit tests for clvm/clvm_ng operations

This commit is contained in:
Pearl Dsilva 2026-03-25 13:05:56 -04:00
parent 437f77c97e
commit 348cf33908
1 changed files with 406 additions and 0 deletions

View File

@ -17,11 +17,17 @@
package com.cloud.hypervisor.kvm.storage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -33,6 +39,7 @@ import org.junit.runner.RunWith;
import org.libvirt.Connect;
import org.libvirt.StoragePool;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@ -45,6 +52,7 @@ import com.cloud.storage.Storage;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtStorageAdaptorTest {
@ -58,6 +66,9 @@ public class LibvirtStorageAdaptorTest {
MockedStatic<Script> mockScript;
// For mocking Script constructor
private MockedConstruction<Script> mockScriptConstruction;
@Spy
static LibvirtStorageAdaptor libvirtStorageAdaptor = new LibvirtStorageAdaptor(null);
@ -73,6 +84,9 @@ public class LibvirtStorageAdaptorTest {
public void tearDown() throws Exception {
libvirtConnectionMockedStatic.close();
mockScript.close();
if (mockScriptConstruction != null) {
mockScriptConstruction.close();
}
closeable.close();
}
@ -176,4 +190,396 @@ public class LibvirtStorageAdaptorTest {
Mockito.verify(mockPool, never()).setUsedIops(anyLong());
}
@Test
public void testGetVolumeFromCLVMPool_ReturnsNullForNonCLVMPool() {
Storage.StoragePoolType type = Storage.StoragePoolType.NetworkFilesystem;
assert type != Storage.StoragePoolType.CLVM;
assert type != Storage.StoragePoolType.CLVM_NG;
}
@Test
public void testCLVMPoolTypeDetection_CLVM() {
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
Storage.StoragePoolType type = mockPool.getType();
assert type == Storage.StoragePoolType.CLVM;
assert type != Storage.StoragePoolType.CLVM_NG;
}
@Test
public void testCLVMPoolTypeDetection_CLVM_NG() {
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
Storage.StoragePoolType type = mockPool.getType();
assert type == Storage.StoragePoolType.CLVM_NG;
assert type != Storage.StoragePoolType.CLVM;
}
@Test
public void testCLVMPoolLocalPathFormat() {
String vgName = "acsvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
String localPath = mockPool.getLocalPath();
assert localPath.equals(vgName);
assert !localPath.startsWith("/");
}
@Test
public void testCLVMNGPoolLocalPathFormat() {
String vgName = "acsvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
String localPath = mockPool.getLocalPath();
assert localPath.equals(vgName);
assert !localPath.startsWith("/");
}
@Test
public void testCLVMVolumePathFormat() {
String vgName = "acsvg";
String volumeUuid = UUID.randomUUID().toString();
String expectedPath = "/dev/" + vgName + "/" + volumeUuid;
assert expectedPath.startsWith("/dev/");
assert expectedPath.contains(vgName);
assert expectedPath.endsWith(volumeUuid);
}
@Test
public void testCLVMNGVolumePathFormat() {
String vgName = "acsvg";
String volumeUuid = UUID.randomUUID().toString();
String expectedPath = "/dev/" + vgName + "/" + volumeUuid;
assert expectedPath.startsWith("/dev/");
assert expectedPath.contains(vgName);
assert expectedPath.endsWith(volumeUuid);
}
@Test
public void testCLVMTemplateVolumeNamingConvention() {
String templateUuid = "550e8400-e29b-41d4-a716-446655440000";
String expectedLvName = "template-" + templateUuid;
assert expectedLvName.startsWith("template-");
assert expectedLvName.contains(templateUuid);
assert expectedLvName.equals("template-550e8400-e29b-41d4-a716-446655440000");
}
@Test
public void testCLVMTemplatePathFormat() {
// CLVM_NG template paths: /dev/vgname/template-{uuid}
String vgName = "acsvg";
String templateUuid = "550e8400-e29b-41d4-a716-446655440000";
String expectedPath = "/dev/" + vgName + "/template-" + templateUuid;
assert expectedPath.equals("/dev/acsvg/template-550e8400-e29b-41d4-a716-446655440000");
assert expectedPath.startsWith("/dev/");
assert expectedPath.contains("template-");
}
@Test
public void testCLVMPoolIsBlockDeviceStorage() {
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
Storage.StoragePoolType type = mockPool.getType();
boolean isBlockBased = (type == Storage.StoragePoolType.CLVM ||
type == Storage.StoragePoolType.CLVM_NG);
assert isBlockBased;
}
@Test
public void testCLVMNGPoolIsBlockDeviceStorage() {
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
Storage.StoragePoolType type = mockPool.getType();
boolean isBlockBased = (type == Storage.StoragePoolType.CLVM ||
type == Storage.StoragePoolType.CLVM_NG);
assert isBlockBased;
}
@Test
public void testCLVMPoolSupportsSharedStorage() {
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.CLVM);
Storage.StoragePoolType type = mockPool.getType();
boolean supportsShared = (type == Storage.StoragePoolType.CLVM ||
type == Storage.StoragePoolType.CLVM_NG);
assert supportsShared;
}
@Test
public void testVolumeGroupNameExtraction() {
String vgName = "acsvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
String extractedVgName = mockPool.getLocalPath();
assert extractedVgName.equals("acsvg");
assert extractedVgName.matches("[a-zA-Z0-9_-]+");
}
@Test
public void testCLVMPoolDetailsContainSecureZeroFill() {
Map<String, String> details = new HashMap<>();
details.put("CLVM_SECURE_ZERO_FILL", "true");
boolean secureZeroEnabled = "true".equals(details.get("CLVM_SECURE_ZERO_FILL"));
assert secureZeroEnabled;
}
@Test
public void testCLVMPoolDetailsSecureZeroFillDisabled() {
Map<String, String> details = new HashMap<>();
details.put("CLVM_SECURE_ZERO_FILL", "false");
boolean secureZeroEnabled = "true".equals(details.get("CLVM_SECURE_ZERO_FILL"));
assert !secureZeroEnabled;
}
@Test
public void testCLVMPoolDetailsSecureZeroFillDefault() {
Map<String, String> details = new HashMap<>();
boolean secureZeroEnabled = "true".equals(details.get("CLVM_SECURE_ZERO_FILL"));
assert !secureZeroEnabled;
}
@Test
public void testCreateCLVMStoragePool_Success() throws Exception {
String uuid = UUID.randomUUID().toString();
String vgName = "testvg";
String host = "localhost";
Connect mockConn = Mockito.mock(Connect.class);
StoragePool mockStoragePool = Mockito.mock(StoragePool.class);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn(null);
});
Mockito.when(mockConn.storagePoolDefineXML(anyString(), eq(0)))
.thenReturn(mockStoragePool);
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createCLVMStoragePool", Connect.class, String.class, String.class, String.class);
method.setAccessible(true);
StoragePool result = (StoragePool) method.invoke(
libvirtStorageAdaptor, mockConn, uuid, host, vgName);
assertNotNull("Storage pool should be created", result);
Mockito.verify(mockStoragePool).setAutostart(1);
Mockito.verify(mockConn).storagePoolDefineXML(anyString(), eq(0));
}
@Test
public void testCreateCLVMStoragePool_VGNotFound() throws Exception {
String uuid = UUID.randomUUID().toString();
String vgName = "nonexistentvg";
String host = "localhost";
Connect mockConn = Mockito.mock(Connect.class);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn("Volume group \"" + vgName + "\" not found");
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createCLVMStoragePool", Connect.class, String.class, String.class, String.class);
method.setAccessible(true);
StoragePool result = (StoragePool) method.invoke(
libvirtStorageAdaptor, mockConn, uuid, host, vgName);
assert result == null : "Should return null when VG doesn't exist";
Mockito.verify(mockConn, never()).storagePoolDefineXML(anyString(), anyInt());
}
@Test
public void testCreateClvmVolume_Success() throws Exception {
String volumeName = UUID.randomUUID().toString();
long size = 10737418240L;
String vgName = "testvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn(null);
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createClvmVolume", String.class, long.class, KVMStoragePool.class);
method.setAccessible(true);
KVMPhysicalDisk result = (KVMPhysicalDisk) method.invoke(
libvirtStorageAdaptor, volumeName, size, mockPool);
assertNotNull("Physical disk should be created", result);
assertEquals("Volume name should match", volumeName, result.getName());
assertEquals("Format should be RAW", PhysicalDiskFormat.RAW, result.getFormat());
assert result.getPath().contains(vgName) : "Path should contain VG name";
assert result.getPath().contains(volumeName) : "Path should contain volume name";
}
@Test(expected = CloudRuntimeException.class)
public void testCreateClvmVolume_LvcreateFailure() throws Throwable {
String volumeName = UUID.randomUUID().toString();
long size = 10737418240L;
String vgName = "testvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn("lvcreate failed: insufficient space");
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createClvmVolume", String.class, long.class, KVMStoragePool.class);
method.setAccessible(true);
try {
method.invoke(libvirtStorageAdaptor, volumeName, size, mockPool);
} catch (java.lang.reflect.InvocationTargetException e) {
throw e.getCause();
}
}
@Test
public void testCreateClvmNgDiskWithBacking_NoBackingFile() throws Exception {
String volumeUuid = UUID.randomUUID().toString();
int timeout = 30000;
long virtualSize = 10737418240L;
String vgName = "testvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn(null);
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createClvmNgDiskWithBacking",
String.class, int.class, long.class, String.class,
KVMStoragePool.class, Storage.ProvisioningType.class);
method.setAccessible(true);
KVMPhysicalDisk result = (KVMPhysicalDisk) method.invoke(
libvirtStorageAdaptor, volumeUuid, timeout, virtualSize, null,
mockPool, Storage.ProvisioningType.THIN);
assertNotNull("Physical disk should be created", result);
assertEquals("Volume UUID should match", volumeUuid, result.getName());
assertEquals("Format should be QCOW2", PhysicalDiskFormat.QCOW2, result.getFormat());
assert result.getPath().contains(vgName) : "Path should contain VG name";
assert result.getPath().contains(volumeUuid) : "Path should contain volume UUID";
}
@Test
public void testCreateClvmNgDiskWithBacking_WithBackingFile() throws Exception {
String volumeUuid = UUID.randomUUID().toString();
String backingFile = "/dev/testvg/parent-volume";
int timeout = 30000;
long virtualSize = 10737418240L;
String vgName = "testvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn(null);
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createClvmNgDiskWithBacking",
String.class, int.class, long.class, String.class,
KVMStoragePool.class, Storage.ProvisioningType.class);
method.setAccessible(true);
KVMPhysicalDisk result = (KVMPhysicalDisk) method.invoke(
libvirtStorageAdaptor, volumeUuid, timeout, virtualSize, backingFile,
mockPool, Storage.ProvisioningType.SPARSE);
assertNotNull("Physical disk should be created", result);
assertEquals("Format should be QCOW2", PhysicalDiskFormat.QCOW2, result.getFormat());
}
@Test(expected = CloudRuntimeException.class)
public void testCreateClvmNgDiskWithBacking_LvcreateFailure() throws Throwable {
String volumeUuid = UUID.randomUUID().toString();
int timeout = 30000;
long virtualSize = 10737418240L;
String vgName = "testvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenReturn("Insufficient free extents");
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createClvmNgDiskWithBacking",
String.class, int.class, long.class, String.class,
KVMStoragePool.class, Storage.ProvisioningType.class);
method.setAccessible(true);
try {
method.invoke(libvirtStorageAdaptor, volumeUuid, timeout, virtualSize,
null, mockPool, Storage.ProvisioningType.THIN);
} catch (java.lang.reflect.InvocationTargetException e) {
throw e.getCause();
}
}
@Test(expected = CloudRuntimeException.class)
public void testCreateClvmNgDiskWithBacking_QemuImgFailure() throws Throwable {
String volumeUuid = UUID.randomUUID().toString();
int timeout = 30000;
long virtualSize = 10737418240L;
String vgName = "testvg";
Mockito.when(mockPool.getLocalPath()).thenReturn(vgName);
final int[] callCount = {0};
mockScriptConstruction = Mockito.mockConstruction(Script.class,
(mock, context) -> {
when(mock.execute()).thenAnswer(invocation -> {
callCount[0]++;
if (callCount[0] == 1) {
return null;
} else {
return "qemu-img: error creating QCOW2";
}
});
});
Method method = LibvirtStorageAdaptor.class.getDeclaredMethod(
"createClvmNgDiskWithBacking",
String.class, int.class, long.class, String.class,
KVMStoragePool.class, Storage.ProvisioningType.class);
method.setAccessible(true);
try {
method.invoke(libvirtStorageAdaptor, volumeUuid, timeout, virtualSize,
null, mockPool, Storage.ProvisioningType.THIN);
} catch (java.lang.reflect.InvocationTargetException e) {
throw e.getCause();
}
}
}