Skip to content

Commit a4faaae

Browse files
GutoVeronezirohityadavcloud
authored andcommitted
Fix: Convert volume to another directory instead of copying it while taking volume snapshots on KVM (apache#8041)
(cherry picked from commit 9b8eaee) Signed-off-by: Rohit Yadav <[email protected]> (cherry picked from commit c7cb9ce) Signed-off-by: Rohit Yadav <[email protected]>
1 parent 9e00645 commit a4faaae

File tree

2 files changed

+66
-28
lines changed

2 files changed

+66
-28
lines changed

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,11 +1567,11 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
15671567
snapshotPath = getSnapshotPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName);
15681568

15691569
String diskLabel = takeVolumeSnapshot(resource.getDisks(conn, vmName), snapshotName, diskPath, vm);
1570-
String copyResult = copySnapshotToPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume);
1570+
String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume, cmd.getWait());
15711571

15721572
mergeSnapshotIntoBaseFile(vm, diskLabel, diskPath, snapshotName, volume, conn);
15731573

1574-
validateCopyResult(copyResult, snapshotPath);
1574+
validateConvertResult(convertResult, snapshotPath);
15751575
} catch (LibvirtException e) {
15761576
if (!e.getMessage().contains(LIBVIRT_OPERATION_NOT_SUPPORTED_MESSAGE)) {
15771577
throw e;
@@ -1636,8 +1636,8 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
16361636
}
16371637
} else {
16381638
snapshotPath = getSnapshotPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName);
1639-
String copyResult = copySnapshotToPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume);
1640-
validateCopyResult(copyResult, snapshotPath);
1639+
String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume, cmd.getWait());
1640+
validateConvertResult(convertResult, snapshotPath);
16411641
}
16421642
}
16431643

@@ -1688,13 +1688,13 @@ protected void takeFullVmSnaphotForBinariesThatDoesNotSupportLiveDiskSnapshot(Do
16881688
s_logger.debug(String.format("Full VM Snapshot [%s] of VM [%s] took [%s] seconds to finish.", snapshotName, vmName, (System.currentTimeMillis() - start)/1000));
16891689
}
16901690

1691-
protected void validateCopyResult(String copyResult, String snapshotPath) throws CloudRuntimeException, IOException {
1692-
if (copyResult == null) {
1691+
protected void validateConvertResult(String convertResult, String snapshotPath) throws CloudRuntimeException, IOException {
1692+
if (convertResult == null) {
16931693
return;
16941694
}
16951695

16961696
Files.deleteIfExists(Paths.get(snapshotPath));
1697-
throw new CloudRuntimeException(copyResult);
1697+
throw new CloudRuntimeException(convertResult);
16981698
}
16991699

17001700
/**
@@ -1751,20 +1751,31 @@ protected void manuallyDeleteUnusedSnapshotFile(boolean isLibvirtSupportingFlagD
17511751
}
17521752

17531753
/**
1754-
* 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..
1754+
* 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.
17551755
* @param primaryPool Storage to create folder, if not exists;
1756-
* @param baseFile Base file of VM, which will be copied;
1757-
* @param snapshotPath Path to copy the base file;
1758-
* @return null if copies successfully or a error message.
1756+
* @param baseFile Base file of VM, which will be converted;
1757+
* @param snapshotPath Path to convert the base file;
1758+
* @return null if the conversion occurs successfully or an error message that must be handled.
17591759
*/
1760-
protected String copySnapshotToPrimaryStorageDir(KVMStoragePool primaryPool, String baseFile, String snapshotPath, VolumeObjectTO volume) {
1760+
protected String convertBaseFileToSnapshotFileInPrimaryStorageDir(KVMStoragePool primaryPool, String baseFile, String snapshotPath, VolumeObjectTO volume, int wait) {
17611761
try {
1762+
s_logger.debug(String.format("Trying to convert volume [%s] (%s) to snapshot [%s].", volume, baseFile, snapshotPath));
1763+
17621764
primaryPool.createFolder(TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR);
1763-
Files.copy(Paths.get(baseFile), Paths.get(snapshotPath));
1764-
s_logger.debug(String.format("Copied %s snapshot from [%s] to [%s].", volume, baseFile, snapshotPath));
1765+
1766+
QemuImgFile srcFile = new QemuImgFile(baseFile);
1767+
srcFile.setFormat(PhysicalDiskFormat.QCOW2);
1768+
1769+
QemuImgFile destFile = new QemuImgFile(snapshotPath);
1770+
destFile.setFormat(PhysicalDiskFormat.QCOW2);
1771+
1772+
QemuImg q = new QemuImg(wait);
1773+
q.convert(srcFile, destFile);
1774+
1775+
s_logger.debug(String.format("Converted volume [%s] (from path \"%s\") to snapshot [%s].", volume, baseFile, snapshotPath));
17651776
return null;
1766-
} catch (IOException ex) {
1767-
return String.format("Unable to copy %s snapshot [%s] to [%s] due to [%s].", volume, baseFile, snapshotPath, ex.getMessage());
1777+
} catch (QemuImgException | LibvirtException ex) {
1778+
return String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volume, baseFile, snapshotPath, ex.getMessage());
17681779
}
17691780
}
17701781

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
import java.util.Set;
3838
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
3939
import org.apache.cloudstack.storage.to.VolumeObjectTO;
40+
import org.apache.cloudstack.utils.qemu.QemuImg;
41+
import org.apache.cloudstack.utils.qemu.QemuImgException;
42+
import org.apache.cloudstack.utils.qemu.QemuImgFile;
4043
import org.junit.Assert;
4144
import org.junit.Before;
4245
import org.junit.Test;
@@ -87,6 +90,9 @@ public class KVMStorageProcessorTest {
8790
@Mock
8891
Connect connectMock;
8992

93+
@Mock
94+
QemuImg qemuImgMock;
95+
9096
private static final String directDownloadTemporaryPath = "/var/lib/libvirt/images/dd";
9197
private static final long templateSize = 80000L;
9298

@@ -241,32 +247,53 @@ public void validateTakeVolumeSnapshotSuccessReturnDiskLabel() throws LibvirtExc
241247

242248
@Test
243249
@PrepareForTest(KVMStorageProcessor.class)
244-
public void validateCopySnapshotToPrimaryStorageDirFailToCopyReturnErrorMessage() throws Exception {
250+
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithQemuImgExceptionReturnErrorMessage() throws Exception {
245251
String baseFile = "baseFile";
246252
String snapshotPath = "snapshotPath";
247253
String errorMessage = "error";
248-
String expectedResult = String.format("Unable to copy %s snapshot [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
254+
String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
249255

250256
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
251-
PowerMockito.mockStatic(Files.class);
252-
PowerMockito.when(Files.copy(Mockito.any(Path.class), Mockito.any(Path.class), Mockito.any())).thenThrow(new IOException(errorMessage));
253257

254-
String result = storageProcessorSpy.copySnapshotToPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock);
258+
PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
259+
Mockito.doThrow(new QemuImgException(errorMessage)).when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
260+
261+
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
255262

256263
Assert.assertEquals(expectedResult, result);
257264
}
258265

259266
@Test
260267
@PrepareForTest(KVMStorageProcessor.class)
261-
public void validateCopySnapshotToPrimaryStorageDirCopySuccessReturnNull() throws Exception {
268+
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithLibvirtExceptionReturnErrorMessage() throws Exception {
262269
String baseFile = "baseFile";
263270
String snapshotPath = "snapshotPath";
271+
String errorMessage = "null";
272+
String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
264273

265274
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
266-
PowerMockito.mockStatic(Files.class);
267-
PowerMockito.when(Files.copy(Mockito.any(Path.class), Mockito.any(Path.class), Mockito.any())).thenReturn(null);
268275

269-
String result = storageProcessorSpy.copySnapshotToPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock);
276+
PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
277+
Mockito.doThrow(LibvirtException.class).when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
278+
279+
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
280+
281+
Assert.assertEquals(expectedResult, result);
282+
}
283+
284+
285+
@Test
286+
@PrepareForTest(KVMStorageProcessor.class)
287+
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestConvertSuccessReturnNull() throws Exception {
288+
String baseFile = "baseFile";
289+
String snapshotPath = "snapshotPath";
290+
291+
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
292+
293+
PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
294+
Mockito.doNothing().when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
295+
296+
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
270297

271298
Assert.assertNull(result);
272299
}
@@ -311,22 +338,22 @@ public void validateIsAvailablePoolSizeDividedByDiskSizeLesserThanMinRate(){
311338

312339
@Test
313340
public void validateValidateCopyResultResultIsNullReturn() throws CloudRuntimeException, IOException{
314-
storageProcessorSpy.validateCopyResult(null, "");
341+
storageProcessorSpy.validateConvertResult(null, "");
315342
}
316343

317344
@Test (expected = IOException.class)
318345
public void validateValidateCopyResultFailToDeleteThrowIOException() throws CloudRuntimeException, IOException{
319346
PowerMockito.mockStatic(Files.class);
320347
PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenThrow(new IOException(""));
321-
storageProcessorSpy.validateCopyResult("", "");
348+
storageProcessorSpy.validateConvertResult("", "");
322349
}
323350

324351
@Test (expected = CloudRuntimeException.class)
325352
@PrepareForTest(KVMStorageProcessor.class)
326353
public void validateValidateCopyResulResultNotNullThrowCloudRuntimeException() throws CloudRuntimeException, IOException{
327354
PowerMockito.mockStatic(Files.class);
328355
PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenReturn(true);
329-
storageProcessorSpy.validateCopyResult("", "");
356+
storageProcessorSpy.validateConvertResult("", "");
330357
}
331358

332359
@Test (expected = CloudRuntimeException.class)

0 commit comments

Comments
 (0)