blob: aab0ce195597fcc802c74e2d44913778f519617a [file] [log] [blame]
/*
* 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.storage;
import com.cloud.exception.InternalErrorException;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper;
import com.cloud.storage.template.TemplateConstants;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import javax.naming.ConfigurationException;
import com.cloud.utils.script.Script;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException;
import org.apache.cloudstack.utils.qemu.QemuImgFile;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.LibvirtException;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@PrepareForTest({ Script.class })
@PowerMockIgnore({"javax.xml.*", "org.xml.*", "org.w3c.dom.*"})
@RunWith(PowerMockRunner.class)
public class KVMStorageProcessorTest {
@Mock
KVMStoragePoolManager storagePoolManager;
@Mock
LibvirtComputingResource resource;
@InjectMocks
private KVMStorageProcessor storageProcessor;
@Spy
KVMStorageProcessor storageProcessorSpy = new KVMStorageProcessor(storagePoolManager, resource);
@Mock
Pair<String, Set<String>> diskToSnapshotAndDisksToAvoidMock;
@Mock
Domain domainMock;
@Mock
KVMStoragePool kvmStoragePoolMock;
@Mock
VolumeObjectTO volumeObjectToMock;
@Mock
SnapshotObjectTO snapshotObjectToMock;
@Mock
Connect connectMock;
@Mock
QemuImg qemuImgMock;
@Mock
LibvirtDomainXMLParser libvirtDomainXMLParserMock;
@Mock
LibvirtVMDef.DiskDef diskDefMock;
private static final String directDownloadTemporaryPath = "/var/lib/libvirt/images/dd";
private static final long templateSize = 80000L;
@Before
public void setUp() throws ConfigurationException {
MockitoAnnotations.initMocks(this);
storageProcessor = new KVMStorageProcessor(storagePoolManager, resource);
}
@Test
public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationAssumeEnoughSpaceWhenNotProvided() {
PowerMockito.mockStatic(Script.class);
Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(null);
Assert.assertTrue(result);
}
@Test
public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationNotEnoughSpace() {
PowerMockito.mockStatic(Script.class);
Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
String output = String.valueOf(templateSize - 30000L);
Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(output);
boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
Assert.assertFalse(result);
}
@Test
public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationEnoughSpace() {
PowerMockito.mockStatic(Script.class);
Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
String output = String.valueOf(templateSize + 30000L);
Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(output);
boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
Assert.assertTrue(result);
}
@Test
public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationNotExistingLocation() {
PowerMockito.mockStatic(Script.class);
Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
String output = String.format("df: ā€˜%sā€™: No such file or directory", directDownloadTemporaryPath);
Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(output);
boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
Assert.assertFalse(result);
}
@Test
public void validateGetSnapshotTemporaryPath(){
String path = "/path/to/disk";
String snapshotName = "snapshot";
String expectedResult = "/path/to/snapshot";
String result = storageProcessor.getSnapshotTemporaryPath(path, snapshotName);
Assert.assertEquals(expectedResult, result);
}
@Test
public void validateGetSnapshotPathInPrimaryStorage(){
String path = "/path/to/disk";
String snapshotName = "snapshot";
String expectedResult = String.format("%s%s%s%s%s", path, File.separator, TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR, File.separator, snapshotName);
String result = storageProcessor.getSnapshotPathInPrimaryStorage(path, snapshotName);
Assert.assertEquals(expectedResult, result);
}
@Test (expected = CloudRuntimeException.class)
public void validateValidateAvailableSizeOnPoolToTakeVolumeSnapshotAvailabeSizeLessThanMinRateThrowCloudRuntimeException(){
KVMPhysicalDisk kvmPhysicalDiskMock = Mockito.mock(KVMPhysicalDisk.class);
Mockito.doReturn(104l).when(kvmStoragePoolMock).getAvailable();
Mockito.doReturn(100l).when(kvmPhysicalDiskMock).getSize();
storageProcessor.validateAvailableSizeOnPoolToTakeVolumeSnapshot(kvmStoragePoolMock, kvmPhysicalDiskMock);
}
@Test
public void validateValidateAvailableSizeOnPoolToTakeVolumeSnapshotAvailabeSizeEqualOrHigherThanMinRateDoNothing(){
KVMPhysicalDisk kvmPhysicalDiskMock = Mockito.mock(KVMPhysicalDisk.class);
Mockito.doReturn(105l, 106l).when(kvmStoragePoolMock).getAvailable();
Mockito.doReturn(100l).when(kvmPhysicalDiskMock).getSize();
storageProcessor.validateAvailableSizeOnPoolToTakeVolumeSnapshot(kvmStoragePoolMock, kvmPhysicalDiskMock);
storageProcessor.validateAvailableSizeOnPoolToTakeVolumeSnapshot(kvmStoragePoolMock, kvmPhysicalDiskMock);
}
private List<LibvirtVMDef.DiskDef> createDiskDefs(int iterations, boolean duplicatePath) {
List<LibvirtVMDef.DiskDef> disks = new ArrayList<>();
for (int i = 1; i <= iterations; i++) {
LibvirtVMDef.DiskDef disk = new LibvirtVMDef.DiskDef();
disk.defFileBasedDisk(String.format("path%s", duplicatePath ? "" : i), String.format("label%s", i), LibvirtVMDef.DiskDef.DiskBus.USB, LibvirtVMDef.DiskDef.DiskFmtType.RAW);
disks.add(disk);
}
return disks;
}
@Test (expected = CloudRuntimeException.class)
public void validateGetDiskToSnapshotAndDisksToAvoidDuplicatePathThrowsCloudRuntimeException() throws LibvirtException{
List<LibvirtVMDef.DiskDef> disks = createDiskDefs(2, true);
storageProcessor.getDiskToSnapshotAndDisksToAvoid(disks, "path", domainMock);
}
@Test (expected = CloudRuntimeException.class)
public void validateGetDiskToSnapshotAndDisksToAvoidPathNotFoundThrowsCloudRuntimeException() throws LibvirtException{
List<LibvirtVMDef.DiskDef> disks = createDiskDefs(5, false);
storageProcessor.getDiskToSnapshotAndDisksToAvoid(disks, "path6", domainMock);
}
@Test
public void validateGetDiskToSnapshotAndDisksToAvoidPathFoundReturnLabels() throws LibvirtException{
List<LibvirtVMDef.DiskDef> disks = createDiskDefs(5, false);
String expectedLabelResult = "label2";
long expectedDisksSizeResult = disks.size() - 1;
Pair<String, Set<String>> result = storageProcessor.getDiskToSnapshotAndDisksToAvoid(disks, "path2", domainMock);
Assert.assertEquals(expectedLabelResult, result.first());
Assert.assertEquals(expectedDisksSizeResult, result.second().size());
}
@Test (expected = LibvirtException.class)
public void validateTakeVolumeSnapshotFailToCreateSnapshotThrowLibvirtException() throws LibvirtException{
Mockito.doReturn(diskToSnapshotAndDisksToAvoidMock).when(storageProcessorSpy).getDiskToSnapshotAndDisksToAvoid(Mockito.any(), Mockito.anyString(), Mockito.any());
Mockito.doReturn("").when(domainMock).getName();
Mockito.doReturn(new HashSet<>()).when(diskToSnapshotAndDisksToAvoidMock).second();
Mockito.doThrow(LibvirtException.class).when(domainMock).snapshotCreateXML(Mockito.anyString(), Mockito.anyInt());
storageProcessorSpy.takeVolumeSnapshot(new ArrayList<>(), "", "", domainMock);
}
@Test
public void validateTakeVolumeSnapshotSuccessReturnDiskLabel() throws LibvirtException{
String expectedResult = "label";
Mockito.doReturn(diskToSnapshotAndDisksToAvoidMock).when(storageProcessorSpy).getDiskToSnapshotAndDisksToAvoid(Mockito.any(), Mockito.anyString(), Mockito.any());
Mockito.doReturn("").when(domainMock).getName();
Mockito.doReturn(expectedResult).when(diskToSnapshotAndDisksToAvoidMock).first();
Mockito.doReturn(new HashSet<>()).when(diskToSnapshotAndDisksToAvoidMock).second();
Mockito.doReturn(null).when(domainMock).snapshotCreateXML(Mockito.anyString(), Mockito.anyInt());
String result = storageProcessorSpy.takeVolumeSnapshot(new ArrayList<>(), "", "", domainMock);
Assert.assertEquals(expectedResult, result);
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithQemuImgExceptionReturnErrorMessage() throws Exception {
String baseFile = "baseFile";
String snapshotPath = "snapshotPath";
String errorMessage = "error";
String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
Mockito.doThrow(new QemuImgException(errorMessage)).when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
Assert.assertEquals(expectedResult, result);
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithLibvirtExceptionReturnErrorMessage() throws Exception {
String baseFile = "baseFile";
String snapshotPath = "snapshotPath";
String errorMessage = "null";
String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
Mockito.doThrow(LibvirtException.class).when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
Assert.assertEquals(expectedResult, result);
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestConvertSuccessReturnNull() throws Exception {
String baseFile = "baseFile";
String snapshotPath = "snapshotPath";
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
Mockito.doNothing().when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
Assert.assertNull(result);
}
@Test (expected = CloudRuntimeException.class)
@PrepareForTest({Script.class, LibvirtUtilitiesHelper.class})
public void validateMergeSnapshotIntoBaseFileErrorOnMergeThrowCloudRuntimeException() throws Exception {
PowerMockito.mockStatic(Script.class, LibvirtUtilitiesHelper.class);
PowerMockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn("");
PowerMockito.when(LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any())).thenReturn(true);
storageProcessorSpy.mergeSnapshotIntoBaseFile(domainMock, "", "", "", volumeObjectToMock, connectMock);
}
@Test
@PrepareForTest({Script.class, LibvirtUtilitiesHelper.class})
public void validateMergeSnapshotIntoBaseFileMergeSuccessDoNothing() throws Exception {
PowerMockito.mockStatic(Script.class, LibvirtUtilitiesHelper.class);
PowerMockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(null);
PowerMockito.when(LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any())).thenReturn(true);
Mockito.doNothing().when(storageProcessorSpy).manuallyDeleteUnusedSnapshotFile(Mockito.anyBoolean(), Mockito.any());
storageProcessorSpy.mergeSnapshotIntoBaseFile(domainMock, "", "", "", volumeObjectToMock, connectMock);
}
@Test (expected = CloudRuntimeException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void validateManuallyDeleteUnusedSnapshotFileLibvirtDoesNotSupportsFlagDeleteExceptionOnFileDeletionThrowsException() throws IOException {
Mockito.doReturn("").when(snapshotObjectToMock).getPath();
PowerMockito.mockStatic(Files.class);
PowerMockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenThrow(IOException.class);
storageProcessorSpy.manuallyDeleteUnusedSnapshotFile(false, "");
}
@Test
public void validateIsAvailablePoolSizeDividedByDiskSizeLesserThanMinRate(){
Assert.assertTrue(storageProcessorSpy.isAvailablePoolSizeDividedByDiskSizeLesserThanMinRate(10499l, 10000l));
Assert.assertFalse(storageProcessorSpy.isAvailablePoolSizeDividedByDiskSizeLesserThanMinRate(10500l, 10000l));
Assert.assertFalse(storageProcessorSpy.isAvailablePoolSizeDividedByDiskSizeLesserThanMinRate(10501l, 10000l));
}
@Test
public void validateValidateCopyResultResultIsNullReturn() throws CloudRuntimeException, IOException{
storageProcessorSpy.validateConvertResult(null, "");
}
@Test (expected = IOException.class)
public void validateValidateCopyResultFailToDeleteThrowIOException() throws CloudRuntimeException, IOException{
PowerMockito.mockStatic(Files.class);
PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenThrow(new IOException(""));
storageProcessorSpy.validateConvertResult("", "");
}
@Test (expected = CloudRuntimeException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void validateValidateCopyResulResultNotNullThrowCloudRuntimeException() throws CloudRuntimeException, IOException{
PowerMockito.mockStatic(Files.class);
PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenReturn(true);
storageProcessorSpy.validateConvertResult("", "");
}
@Test (expected = CloudRuntimeException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void validateDeleteSnapshotFileErrorOnDeleteThrowsCloudRuntimeException() throws Exception {
Mockito.doReturn("").when(snapshotObjectToMock).getPath();
PowerMockito.mockStatic(Files.class);
PowerMockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenThrow(IOException.class);
storageProcessorSpy.deleteSnapshotFile(snapshotObjectToMock);
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void validateDeleteSnapshotFileSuccess () throws IOException {
Mockito.doReturn("").when(snapshotObjectToMock).getPath();
PowerMockito.mockStatic(Files.class);
PowerMockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenReturn(true);
storageProcessorSpy.deleteSnapshotFile(snapshotObjectToMock);
}
private void checkDetachSucessTest(boolean duplicate) throws Exception {
List<LibvirtVMDef.DiskDef> disks = createDiskDefs(2, duplicate);
PowerMockito.when(domainMock.getXMLDesc(Mockito.anyInt())).thenReturn("test");
PowerMockito.whenNew(LibvirtDomainXMLParser.class).withAnyArguments().thenReturn(libvirtDomainXMLParserMock);
PowerMockito.when(libvirtDomainXMLParserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
PowerMockito.when(libvirtDomainXMLParserMock.getDisks()).thenReturn(disks);
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void checkDetachSucessTestDetachReturnTrue() throws Exception {
checkDetachSucessTest(false);
Assert.assertTrue(storageProcessorSpy.checkDetachSuccess("path", domainMock));
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void checkDetachSucessTestDetachReturnFalse() throws Exception {
checkDetachSucessTest(true);
Assert.assertFalse(storageProcessorSpy.checkDetachSuccess("path", domainMock));
}
private void attachOrDetachDeviceTest (boolean attach, String vmName, LibvirtVMDef.DiskDef xml) throws LibvirtException, InternalErrorException {
storageProcessorSpy.attachOrDetachDevice(connectMock, attach, vmName, xml);
}
@PrepareForTest(KVMStorageProcessor.class)
private void attachOrDetachDeviceTest (boolean attach, String vmName, LibvirtVMDef.DiskDef xml, long waitDetachDevice) throws LibvirtException, InternalErrorException {
storageProcessorSpy.attachOrDetachDevice(connectMock, attach, vmName, xml, waitDetachDevice);
}
@Test (expected = LibvirtException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void attachOrDetachDeviceTestThrowLibvirtException() throws LibvirtException, InternalErrorException {
Mockito.when(connectMock.domainLookupByName(Mockito.anyString())).thenThrow(LibvirtException.class);
attachOrDetachDeviceTest(true, "vmName", diskDefMock);
}
@PrepareForTest(KVMStorageProcessor.class)
public void attachOrDetachDeviceTestAttachSuccess() throws LibvirtException, InternalErrorException {
Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
attachOrDetachDeviceTest(true, "vmName", diskDefMock);
Mockito.verify(domainMock, Mockito.times(1)).attachDevice(Mockito.anyString());
}
@Test (expected = LibvirtException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void attachOrDetachDeviceTestAttachThrowLibvirtException() throws LibvirtException, InternalErrorException {
Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
Mockito.when(diskDefMock.toString()).thenReturn("diskDef");
Mockito.when(diskDefMock.getDiskPath()).thenReturn("diskDef");
Mockito.doThrow(LibvirtException.class).when(domainMock).attachDevice(Mockito.anyString());
attachOrDetachDeviceTest(true, "vmName", diskDefMock);
}
@Test (expected = LibvirtException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void attachOrDetachDeviceTestDetachThrowLibvirtException() throws LibvirtException, InternalErrorException {
Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
Mockito.doThrow(LibvirtException.class).when(domainMock).detachDevice(Mockito.anyString());
attachOrDetachDeviceTest(false, "vmName", diskDefMock);
}
@Test
@PrepareForTest(KVMStorageProcessor.class)
public void attachOrDetachDeviceTestDetachSuccess() throws LibvirtException, InternalErrorException {
Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
PowerMockito.doReturn(true).when(storageProcessorSpy).checkDetachSuccess(Mockito.anyString(), Mockito.any(Domain.class));
Mockito.when(diskDefMock.toString()).thenReturn("diskDef");
Mockito.when(diskDefMock.getDiskPath()).thenReturn("diskDef");
attachOrDetachDeviceTest( false, "vmName", diskDefMock, 10000);
Mockito.verify(domainMock, Mockito.times(1)).detachDevice(Mockito.anyString());
}
@Test (expected = InternalErrorException.class)
@PrepareForTest(KVMStorageProcessor.class)
public void attachOrDetachDeviceTestDetachThrowInternalErrorException() throws LibvirtException, InternalErrorException {
Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
PowerMockito.doReturn(false).when(storageProcessorSpy).checkDetachSuccess(Mockito.anyString(), Mockito.any(Domain.class));
Mockito.when(diskDefMock.toString()).thenReturn("diskDef");
Mockito.when(diskDefMock.getDiskPath()).thenReturn("diskDef");
attachOrDetachDeviceTest( false, "vmName", diskDefMock);
Mockito.verify(domainMock, Mockito.times(1)).detachDevice(Mockito.anyString());
}
}