This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch 4.18
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.18 by this push:
     new 9b8eaeea78a Fix: Convert volume to another directory instead of 
copying it while taking volume snapshots on KVM (#8041)
9b8eaeea78a is described below

commit 9b8eaeea78a203926f0fc00e948ce30cfb2eb519
Author: Daniel Augusto Veronezi Salvador 
<[email protected]>
AuthorDate: Fri Oct 6 04:47:34 2023 -0300

    Fix: Convert volume to another directory instead of copying it while taking 
volume snapshots on KVM (#8041)
---
 .../kvm/storage/KVMStorageProcessor.java           | 43 +++++++++++-------
 .../kvm/storage/KVMStorageProcessorTest.java       | 51 +++++++++++++++++-----
 2 files changed, 66 insertions(+), 28 deletions(-)

diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index 69c28d4350a..ae0fa637bf0 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -1715,11 +1715,11 @@ public class KVMStorageProcessor implements 
StorageProcessor {
                     snapshotPath = 
getSnapshotPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName);
 
                     String diskLabel = 
takeVolumeSnapshot(resource.getDisks(conn, vmName), snapshotName, diskPath, vm);
-                    String copyResult = 
copySnapshotToPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume);
+                    String convertResult = 
convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, diskPath, 
snapshotPath, volume, cmd.getWait());
 
                     mergeSnapshotIntoBaseFile(vm, diskLabel, diskPath, 
snapshotName, volume, conn);
 
-                    validateCopyResult(copyResult, snapshotPath);
+                    validateConvertResult(convertResult, snapshotPath);
                 } catch (LibvirtException e) {
                     if 
(!e.getMessage().contains(LIBVIRT_OPERATION_NOT_SUPPORTED_MESSAGE)) {
                         throw e;
@@ -1784,8 +1784,8 @@ public class KVMStorageProcessor implements 
StorageProcessor {
                     }
                 } else {
                     snapshotPath = 
getSnapshotPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName);
-                    String copyResult = 
copySnapshotToPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume);
-                    validateCopyResult(copyResult, snapshotPath);
+                    String convertResult = 
convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, diskPath, 
snapshotPath, volume, cmd.getWait());
+                    validateConvertResult(convertResult, snapshotPath);
                 }
             }
 
@@ -1838,13 +1838,13 @@ public class KVMStorageProcessor implements 
StorageProcessor {
         s_logger.debug(String.format("Full VM Snapshot [%s] of VM [%s] took 
[%s] seconds to finish.", snapshotName, vmName, (System.currentTimeMillis() - 
start)/1000));
     }
 
-    protected void validateCopyResult(String copyResult, String snapshotPath) 
throws CloudRuntimeException, IOException {
-        if (copyResult == null) {
+    protected void validateConvertResult(String convertResult, String 
snapshotPath) throws CloudRuntimeException, IOException {
+        if (convertResult == null) {
             return;
         }
 
         Files.deleteIfExists(Paths.get(snapshotPath));
-        throw new CloudRuntimeException(copyResult);
+        throw new CloudRuntimeException(convertResult);
     }
 
     /**
@@ -1901,20 +1901,31 @@ public class KVMStorageProcessor implements 
StorageProcessor {
     }
 
     /**
-     * Creates the snapshot directory in the primary storage, if it does not 
exist; then copies the base file (VM's old writing file) to the snapshot dir..
+     * Creates the snapshot directory in the primary storage, if it does not 
exist; then, converts the base file (VM's old writing file) to the snapshot 
directory.
      * @param primaryPool Storage to create folder, if not exists;
-     * @param baseFile Base file of VM, which will be copied;
-     * @param snapshotPath Path to copy the base file;
-     * @return null if copies successfully or a error message.
+     * @param baseFile Base file of VM, which will be converted;
+     * @param snapshotPath Path to convert the base file;
+     * @return null if the conversion occurs successfully or an error message 
that must be handled.
      */
-    protected String copySnapshotToPrimaryStorageDir(KVMStoragePool 
primaryPool, String baseFile, String snapshotPath, VolumeObjectTO volume) {
+    protected String 
convertBaseFileToSnapshotFileInPrimaryStorageDir(KVMStoragePool primaryPool, 
String baseFile, String snapshotPath, VolumeObjectTO volume, int wait) {
         try {
+            s_logger.debug(String.format("Trying to convert volume [%s] (%s) 
to snapshot [%s].", volume, baseFile, snapshotPath));
+
             
primaryPool.createFolder(TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR);
-            Files.copy(Paths.get(baseFile), Paths.get(snapshotPath));
-            s_logger.debug(String.format("Copied %s snapshot from [%s] to 
[%s].", volume, baseFile, snapshotPath));
+
+            QemuImgFile srcFile = new QemuImgFile(baseFile);
+            srcFile.setFormat(PhysicalDiskFormat.QCOW2);
+
+            QemuImgFile destFile = new QemuImgFile(snapshotPath);
+            destFile.setFormat(PhysicalDiskFormat.QCOW2);
+
+            QemuImg q = new QemuImg(wait);
+            q.convert(srcFile, destFile);
+
+            s_logger.debug(String.format("Converted volume [%s] (from path 
\"%s\") to snapshot [%s].", volume, baseFile, snapshotPath));
             return null;
-        } catch (IOException ex) {
-            return String.format("Unable to copy %s snapshot [%s] to [%s] due 
to [%s].", volume, baseFile, snapshotPath, ex.getMessage());
+        } catch (QemuImgException | LibvirtException ex) {
+            return String.format("Failed to convert %s snapshot of volume [%s] 
to [%s] due to [%s].", volume, baseFile, snapshotPath, ex.getMessage());
         }
     }
 
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
index 98a5b130bae..aab0ce19559 100644
--- 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
@@ -39,6 +39,9 @@ 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;
@@ -91,6 +94,9 @@ public class KVMStorageProcessorTest {
     @Mock
     Connect connectMock;
 
+    @Mock
+    QemuImg qemuImgMock;
+
     @Mock
     LibvirtDomainXMLParser libvirtDomainXMLParserMock;
     @Mock
@@ -251,32 +257,53 @@ public class KVMStorageProcessorTest {
 
     @Test
     @PrepareForTest(KVMStorageProcessor.class)
-    public void 
validateCopySnapshotToPrimaryStorageDirFailToCopyReturnErrorMessage() throws 
Exception {
+    public void 
convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithQemuImgExceptionReturnErrorMessage()
 throws Exception {
         String baseFile = "baseFile";
         String snapshotPath = "snapshotPath";
         String errorMessage = "error";
-        String expectedResult = String.format("Unable to copy %s snapshot [%s] 
to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, 
errorMessage);
+        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.mockStatic(Files.class);
-        PowerMockito.when(Files.copy(Mockito.any(Path.class), 
Mockito.any(Path.class), Mockito.any())).thenThrow(new 
IOException(errorMessage));
 
-        String result = 
storageProcessorSpy.copySnapshotToPrimaryStorageDir(kvmStoragePoolMock, 
baseFile, snapshotPath, volumeObjectToMock);
+        
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 validateCopySnapshotToPrimaryStorageDirCopySuccessReturnNull() 
throws Exception {
+    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.mockStatic(Files.class);
-        PowerMockito.when(Files.copy(Mockito.any(Path.class), 
Mockito.any(Path.class), Mockito.any())).thenReturn(null);
 
-        String result = 
storageProcessorSpy.copySnapshotToPrimaryStorageDir(kvmStoragePoolMock, 
baseFile, snapshotPath, volumeObjectToMock);
+        
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);
     }
@@ -321,14 +348,14 @@ public class KVMStorageProcessorTest {
 
     @Test
     public void validateValidateCopyResultResultIsNullReturn() throws 
CloudRuntimeException, IOException{
-        storageProcessorSpy.validateCopyResult(null, "");
+        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.validateCopyResult("", "");
+        storageProcessorSpy.validateConvertResult("", "");
     }
 
     @Test (expected = CloudRuntimeException.class)
@@ -336,7 +363,7 @@ public class KVMStorageProcessorTest {
     public void 
validateValidateCopyResulResultNotNullThrowCloudRuntimeException() throws 
CloudRuntimeException, IOException{
         PowerMockito.mockStatic(Files.class);
         
PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenReturn(true);
-        storageProcessorSpy.validateCopyResult("", "");
+        storageProcessorSpy.validateConvertResult("", "");
     }
 
     @Test (expected = CloudRuntimeException.class)

Reply via email to