Skip to content

Commit 9618a96

Browse files
slavkapdhslove
authored andcommitted
Fix volume snapshot of encrypted NFS/StorPool volume (apache#8873)
* Fix volume snapshot of encrypted NFS/StorPool volume * remove comments * removed invoking the real qemu convert command * fix UnsatisfiedLink error in unit tests * addressed comments extracted method
1 parent 3ec5f1a commit 9618a96

3 files changed

Lines changed: 123 additions & 59 deletions

File tree

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

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@
134134
import com.cloud.utils.script.Script;
135135
import com.cloud.utils.storage.S3.S3Utils;
136136
import com.cloud.vm.VmDetailConstants;
137+
import org.apache.cloudstack.utils.cryptsetup.KeyFile;
138+
import org.apache.cloudstack.utils.qemu.QemuImageOptions;
139+
import org.apache.cloudstack.utils.qemu.QemuObject.EncryptFormat;
140+
import java.util.ArrayList;
137141

138142
public class KVMStorageProcessor implements StorageProcessor {
139143
protected Logger logger = LogManager.getLogger(getClass());
@@ -1795,7 +1799,7 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
17951799
snapshotPath = getSnapshotPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName);
17961800

17971801
String diskLabel = takeVolumeSnapshot(resource.getDisks(conn, vmName), snapshotName, diskPath, vm);
1798-
String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume, cmd.getWait());
1802+
String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, disk, snapshotPath, volume, cmd.getWait());
17991803

18001804
mergeSnapshotIntoBaseFile(vm, diskLabel, diskPath, snapshotName, volume, conn);
18011805

@@ -1864,7 +1868,7 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
18641868
}
18651869
} else {
18661870
snapshotPath = getSnapshotPathInPrimaryStorage(primaryPool.getLocalPath(), snapshotName);
1867-
String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, diskPath, snapshotPath, volume, cmd.getWait());
1871+
String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPool, disk, snapshotPath, volume, cmd.getWait());
18681872
validateConvertResult(convertResult, snapshotPath);
18691873
}
18701874
}
@@ -1987,26 +1991,43 @@ protected void manuallyDeleteUnusedSnapshotFile(boolean isLibvirtSupportingFlagD
19871991
* @param snapshotPath Path to convert the base file;
19881992
* @return null if the conversion occurs successfully or an error message that must be handled.
19891993
*/
1990-
protected String convertBaseFileToSnapshotFileInPrimaryStorageDir(KVMStoragePool primaryPool, String baseFile, String snapshotPath, VolumeObjectTO volume, int wait) {
1991-
try {
1992-
logger.debug(String.format("Trying to convert volume [%s] (%s) to snapshot [%s].", volume, baseFile, snapshotPath));
1994+
protected String convertBaseFileToSnapshotFileInPrimaryStorageDir(KVMStoragePool primaryPool,
1995+
KVMPhysicalDisk baseFile, String snapshotPath, VolumeObjectTO volume, int wait) {
1996+
try (KeyFile srcKey = new KeyFile(volume.getPassphrase())) {
1997+
logger.debug(
1998+
String.format("Trying to convert volume [%s] (%s) to snapshot [%s].", volume, baseFile, snapshotPath));
19931999

19942000
primaryPool.createFolder(TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR);
2001+
convertTheBaseFileToSnapshot(baseFile, snapshotPath, wait, srcKey);
2002+
} catch (QemuImgException | LibvirtException | IOException ex) {
2003+
return String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volume, baseFile,
2004+
snapshotPath, ex.getMessage());
2005+
}
19952006

1996-
QemuImgFile srcFile = new QemuImgFile(baseFile);
1997-
srcFile.setFormat(PhysicalDiskFormat.QCOW2);
2007+
logger.debug(String.format("Converted volume [%s] (from path \"%s\") to snapshot [%s].", volume, baseFile,
2008+
snapshotPath));
2009+
return null;
2010+
}
19982011

1999-
QemuImgFile destFile = new QemuImgFile(snapshotPath);
2000-
destFile.setFormat(PhysicalDiskFormat.QCOW2);
2012+
private void convertTheBaseFileToSnapshot(KVMPhysicalDisk baseFile, String snapshotPath, int wait, KeyFile srcKey)
2013+
throws LibvirtException, QemuImgException {
2014+
List<QemuObject> qemuObjects = new ArrayList<>();
2015+
Map<String, String> options = new HashMap<>();
2016+
QemuImageOptions qemuImageOpts = new QemuImageOptions(baseFile.getPath());
2017+
if (srcKey.isSet()) {
2018+
String srcKeyName = "sec0";
2019+
qemuObjects.add(QemuObject.prepareSecretForQemuImg(baseFile.getFormat(), EncryptFormat.LUKS,
2020+
srcKey.toString(), srcKeyName, options));
2021+
qemuImageOpts = new QemuImageOptions(baseFile.getFormat(), baseFile.getPath(), srcKeyName);
2022+
}
2023+
QemuImgFile srcFile = new QemuImgFile(baseFile.getPath());
2024+
srcFile.setFormat(PhysicalDiskFormat.QCOW2);
20012025

2002-
QemuImg q = new QemuImg(wait);
2003-
q.convert(srcFile, destFile);
2026+
QemuImgFile destFile = new QemuImgFile(snapshotPath);
2027+
destFile.setFormat(PhysicalDiskFormat.QCOW2);
20042028

2005-
logger.debug(String.format("Converted volume [%s] (from path \"%s\") to snapshot [%s].", volume, baseFile, snapshotPath));
2006-
return null;
2007-
} catch (QemuImgException | LibvirtException ex) {
2008-
return String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volume, baseFile, snapshotPath, ex.getMessage());
2009-
}
2029+
QemuImg q = new QemuImg(wait);
2030+
q.convert(srcFile, destFile, options, qemuObjects, qemuImageOpts, null, true);
20102031
}
20112032

20122033
/**

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

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,10 @@
2626
import com.cloud.storage.template.TemplateConstants;
2727
import com.cloud.utils.Pair;
2828
import com.cloud.utils.exception.CloudRuntimeException;
29-
import javax.naming.ConfigurationException;
30-
3129
import com.cloud.utils.script.Script;
32-
import java.io.File;
33-
import java.io.IOException;
34-
import java.nio.file.Files;
35-
import java.nio.file.Path;
36-
import java.util.ArrayList;
37-
import java.util.HashSet;
38-
import java.util.List;
39-
import java.util.Set;
4030
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
4131
import org.apache.cloudstack.storage.to.VolumeObjectTO;
32+
import org.apache.cloudstack.utils.qemu.QemuImageOptions;
4233
import org.apache.cloudstack.utils.qemu.QemuImg;
4334
import org.apache.cloudstack.utils.qemu.QemuImgException;
4435
import org.apache.cloudstack.utils.qemu.QemuImgFile;
@@ -59,6 +50,17 @@
5950
import org.mockito.Spy;
6051
import org.mockito.junit.MockitoJUnitRunner;
6152

53+
import javax.naming.ConfigurationException;
54+
import java.io.File;
55+
import java.io.IOException;
56+
import java.nio.file.Files;
57+
import java.nio.file.Path;
58+
import java.util.ArrayList;
59+
import java.util.HashSet;
60+
import java.util.List;
61+
import java.util.Map;
62+
import java.util.Set;
63+
6264
@RunWith(MockitoJUnitRunner.class)
6365
public class KVMStorageProcessorTest {
6466

@@ -259,40 +261,48 @@ public void validateTakeVolumeSnapshotSuccessReturnDiskLabel() throws LibvirtExc
259261
}
260262

261263
@Test
262-
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithQemuImgExceptionReturnErrorMessage() throws Exception {
263-
String baseFile = "baseFile";
264-
String snapshotPath = "snapshotPath";
264+
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithQemuImgExceptionReturnErrorMessage() throws QemuImgException {
265+
KVMPhysicalDisk baseFile = Mockito.mock(KVMPhysicalDisk.class);
265266
String errorMessage = "error";
266-
String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
267-
268-
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
269-
try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock,context) -> {
270-
Mockito.doThrow(new QemuImgException(errorMessage)).when(mock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
271-
})) {
272-
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
273-
Assert.assertEquals(expectedResult, result);
267+
KVMStoragePool primaryPoolMock = Mockito.mock(KVMStoragePool.class);
268+
KVMPhysicalDisk baseFileMock = Mockito.mock(KVMPhysicalDisk.class);
269+
VolumeObjectTO volumeMock = Mockito.mock(VolumeObjectTO.class);
270+
QemuImgFile srcFileMock = Mockito.mock(QemuImgFile.class);
271+
QemuImgFile destFileMock = Mockito.mock(QemuImgFile.class);
272+
QemuImg qemuImgMock = Mockito.mock(QemuImg.class);
273+
274+
Mockito.when(baseFileMock.getPath()).thenReturn("/path/to/baseFile");
275+
Mockito.when(primaryPoolMock.createFolder(Mockito.anyString())).thenReturn(true);
276+
try (MockedConstruction<Script> scr = Mockito.mockConstruction(Script.class, ((mock, context) -> {
277+
Mockito.doReturn("").when(mock).execute();
278+
}));
279+
MockedConstruction<QemuImg> qemu = Mockito.mockConstruction(QemuImg.class, ((mock, context) -> {
280+
Mockito.lenient().doThrow(new QemuImgException(errorMessage)).when(mock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class), Mockito.any(Map.class),
281+
Mockito.any(List.class), Mockito.any(QemuImageOptions.class),Mockito.nullable(String.class), Mockito.any(Boolean.class));
282+
}))) {
283+
String test = storageProcessor.convertBaseFileToSnapshotFileInPrimaryStorageDir(primaryPoolMock, baseFileMock, "/path/to/snapshot", volumeMock, 0);
284+
Assert.assertNotNull(test);
274285
}
275286
}
276287

277288
@Test
278289
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithLibvirtExceptionReturnErrorMessage() throws Exception {
279-
String baseFile = "baseFile";
290+
KVMPhysicalDisk baseFile = Mockito.mock(KVMPhysicalDisk.class);
280291
String snapshotPath = "snapshotPath";
281-
String errorMessage = "null";
282-
String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
292+
QemuImg qemuImg = Mockito.mock(QemuImg.class);
283293

284294
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
285-
try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock,context) -> {
286-
Mockito.doThrow(LibvirtException.class).when(mock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
295+
try (MockedConstruction<QemuImg> ignored = Mockito.mockConstructionWithAnswer(QemuImg.class, invocation -> {
296+
throw Mockito.mock(LibvirtException.class);
287297
})) {
288298
String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
289-
Assert.assertEquals(expectedResult, result);
299+
Assert.assertNotNull(result);
290300
}
291301
}
292302

293303
@Test
294304
public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestConvertSuccessReturnNull() throws Exception {
295-
String baseFile = "baseFile";
305+
KVMPhysicalDisk baseFile = Mockito.mock(KVMPhysicalDisk.class);
296306
String snapshotPath = "snapshotPath";
297307

298308
Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());

plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolBackupSnapshotCommandWrapper.java

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,22 @@
2222
import static com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor.SP_LOG;
2323

2424
import java.io.File;
25+
import java.io.IOException;
26+
import java.util.ArrayList;
27+
import java.util.HashMap;
28+
import java.util.List;
29+
import java.util.Map;
2530

2631
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
2732
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
33+
import org.apache.cloudstack.utils.cryptsetup.KeyFile;
34+
import org.apache.cloudstack.utils.qemu.QemuImageOptions;
2835
import org.apache.cloudstack.utils.qemu.QemuImg;
2936
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
37+
import org.apache.cloudstack.utils.qemu.QemuImgException;
3038
import org.apache.cloudstack.utils.qemu.QemuImgFile;
39+
import org.apache.cloudstack.utils.qemu.QemuObject;
40+
import org.apache.cloudstack.utils.qemu.QemuObject.EncryptFormat;
3141
import org.apache.commons.io.FileUtils;
3242

3343
import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
@@ -57,28 +67,24 @@ public CopyCmdAnswer execute(final StorPoolBackupSnapshotCommand cmd, final Libv
5767
SP_LOG("StorpoolBackupSnapshotCommandWrapper.execute: src=" + src.getPath() + "dst=" + dst.getPath());
5868
StorPoolStorageAdaptor.attachOrDetachVolume("attach", "snapshot", src.getPath());
5969
srcPath = src.getPath();
60-
61-
final QemuImgFile srcFile = new QemuImgFile(srcPath, PhysicalDiskFormat.RAW);
62-
70+
long size = 0;
71+
String srcKeyName = "sec0";
72+
String destKeyName = "sec1";
73+
List<QemuObject> qemuObjects = new ArrayList<>();
74+
Map<String, String> options = new HashMap<>();
75+
QemuImageOptions qemuImageOpts = new QemuImageOptions(srcPath);
76+
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
6377
final DataStoreTO dstDataStore = dst.getDataStore();
6478
if (!(dstDataStore instanceof NfsTO)) {
6579
return new CopyCmdAnswer("Backup Storpool snapshot: Only NFS secondary supported at present!");
6680
}
6781

6882
secondaryPool = storagePoolMgr.getStoragePoolByURI(dstDataStore.getUrl());
83+
try (KeyFile srcKey = new KeyFile(src.getVolume().getPassphrase())) {
6984

70-
final String dstDir = secondaryPool.getLocalPath() + File.separator + dst.getPath();
71-
FileUtils.forceMkdir(new File(dstDir));
72-
73-
final String dstPath = dstDir + File.separator + dst.getName();
74-
final QemuImgFile dstFile = new QemuImgFile(dstPath, PhysicalDiskFormat.QCOW2);
75-
76-
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
77-
qemu.convert(srcFile, dstFile);
78-
79-
SP_LOG("StorpoolBackupSnapshotCommandWrapper srcFileFormat=%s, dstFileFormat=%s", srcFile.getFormat(), dstFile.getFormat());
80-
final File snapFile = new File(dstPath);
81-
final long size = snapFile.exists() ? snapFile.length() : 0;
85+
size = convertSnapshot(srcPath, secondaryPool, dst, srcKeyName, qemuObjects, options, qemuImageOpts,
86+
qemu, srcKey);
87+
}
8288

8389
final SnapshotObjectTO snapshot = new SnapshotObjectTO();
8490
snapshot.setPath(dst.getPath() + File.separator + dst.getName());
@@ -104,4 +110,31 @@ public CopyCmdAnswer execute(final StorPoolBackupSnapshotCommand cmd, final Libv
104110
}
105111
}
106112
}
113+
114+
private long convertSnapshot(String srcPath, KVMStoragePool secondaryPool, final SnapshotObjectTO dst,
115+
String srcKeyName, List<QemuObject> qemuObjects, Map<String, String> options,
116+
QemuImageOptions qemuImageOpts, final QemuImg qemu, KeyFile srcKey) throws IOException, QemuImgException {
117+
long size;
118+
final QemuImgFile srcFile = new QemuImgFile(srcPath, PhysicalDiskFormat.RAW);
119+
120+
final String dstDir = secondaryPool.getLocalPath() + File.separator + dst.getPath();
121+
FileUtils.forceMkdir(new File(dstDir));
122+
123+
final String dstPath = dstDir + File.separator + dst.getName();
124+
final QemuImgFile dstFile = new QemuImgFile(dstPath, PhysicalDiskFormat.QCOW2);
125+
if (srcKey.isSet()) {
126+
qemuObjects.add(QemuObject.prepareSecretForQemuImg(PhysicalDiskFormat.RAW, EncryptFormat.LUKS,
127+
srcKey.toString(), srcKeyName, options));
128+
qemuImageOpts = new QemuImageOptions(PhysicalDiskFormat.RAW, srcPath, srcKeyName);
129+
dstFile.setFormat(PhysicalDiskFormat.LUKS);
130+
}
131+
132+
qemuImageOpts.setImageOptsFlag(true);
133+
qemu.convert(srcFile, dstFile, options, qemuObjects, qemuImageOpts, null, true);
134+
135+
SP_LOG("StorpoolBackupSnapshotCommandWrapper srcFileFormat=%s, dstFileFormat=%s", srcFile.getFormat(), dstFile.getFormat());
136+
final File snapFile = new File(dstPath);
137+
size = snapFile.exists() ? snapFile.length() : 0;
138+
return size;
139+
}
107140
}

0 commit comments

Comments
 (0)