Skip to content

Commit ec0f8bd

Browse files
Marcus SorensenMarcus Sorensen
andauthored
Support local storage live migration for direct download templates (#7453)
Co-authored-by: Marcus Sorensen <mls@apple.com>
1 parent d147f1c commit ec0f8bd

8 files changed

Lines changed: 124 additions & 9 deletions

File tree

core/src/main/java/com/cloud/agent/api/MigrateCommand.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ public String toString() {
171171
private final DriverType driverType;
172172
private final Source source;
173173
private final String sourceText;
174+
private final String backingStoreText;
174175
private boolean isSourceDiskOnStorageFileSystem;
175176

176177
public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText) {
@@ -179,6 +180,16 @@ public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final
179180
this.driverType = driverType;
180181
this.source = source;
181182
this.sourceText = sourceText;
183+
this.backingStoreText = null;
184+
}
185+
186+
public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText, final String backingStoreText) {
187+
this.serialNumber = serialNumber;
188+
this.diskType = diskType;
189+
this.driverType = driverType;
190+
this.source = source;
191+
this.sourceText = sourceText;
192+
this.backingStoreText = backingStoreText;
182193
}
183194

184195
public String getSerialNumber() {
@@ -201,6 +212,8 @@ public String getSourceText() {
201212
return sourceText;
202213
}
203214

215+
public String getBackingStoreText() { return backingStoreText; }
216+
204217
public boolean isSourceDiskOnStorageFileSystem() {
205218
return isSourceDiskOnStorageFileSystem;
206219
}

core/src/main/java/org/apache/cloudstack/storage/to/TemplateObjectTO.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public TemplateObjectTO(TemplateInfo template) {
8686
this.hypervisorType = template.getHypervisorType();
8787
this.deployAsIs = template.isDeployAsIs();
8888
this.deployAsIsConfiguration = template.getDeployAsIsConfiguration();
89+
this.directDownload = template.isDirectDownload();
8990
}
9091

9192
@Override

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,9 @@ protected boolean isDestinationNfsPrimaryStorageClusterWide(Map<VolumeInfo, Data
149149
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume and calls rootImageProvisioning.
150150
*/
151151
@Override
152-
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath) {
153-
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
154-
MigrateCommand.MigrateDiskInfo.Source.FILE, destPath);
152+
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath, String backingPath) {
153+
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
154+
MigrateCommand.MigrateDiskInfo.Source.FILE, destPath, backingPath);
155155
}
156156

157157
/**
@@ -163,6 +163,15 @@ protected String generateDestPath(Host destHost, StoragePoolVO destStoragePool,
163163
return new File(destStoragePool.getPath(), destVolumeInfo.getUuid()).getAbsolutePath();
164164
}
165165

166+
@Override
167+
protected String generateBackingPath(StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
168+
String templateInstallPath = getVolumeBackingFile(destVolumeInfo);
169+
if (templateInstallPath == null) {
170+
return null;
171+
}
172+
return new File(destStoragePool.getPath(), templateInstallPath).getAbsolutePath();
173+
}
174+
166175
/**
167176
* Returns the template UUID with the given id. If the template ID is null, it returns null.
168177
*/
@@ -201,6 +210,12 @@ protected void copyTemplateToTargetFilesystemStorageIfNeeded(VolumeInfo srcVolum
201210
return;
202211
}
203212

213+
TemplateInfo directDownloadTemplateInfo = templateDataFactory.getReadyBypassedTemplateOnPrimaryStore(srcVolumeInfo.getTemplateId(), destDataStore.getId(), destHost.getId());
214+
if (directDownloadTemplateInfo != null) {
215+
LOGGER.debug(String.format("Template %s was of direct download type and successfully staged to primary store %s", directDownloadTemplateInfo.getId(), directDownloadTemplateInfo.getDataStore().getId()));
216+
return;
217+
}
218+
204219
VMTemplateStoragePoolVO sourceVolumeTemplateStoragePoolVO = vmTemplatePoolDao.findByPoolTemplate(destStoragePool.getId(), srcVolumeInfo.getTemplateId(), null);
205220
if (sourceVolumeTemplateStoragePoolVO == null && (isStoragePoolTypeInList(destStoragePool.getPoolType(), StoragePoolType.Filesystem, StoragePoolType.SharedMountPoint))) {
206221
DataStore sourceTemplateDataStore = dataStoreManagerImpl.getRandomImageStore(srcVolumeInfo.getDataCenterId());

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,7 +1871,8 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
18711871
MigrateCommand.MigrateDiskInfo.Source.FILE,
18721872
connectHostToVolume(destHost, destVolumeInfo.getPoolId(), volumeIdentifier));
18731873
} else {
1874-
migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath);
1874+
String backingPath = generateBackingPath(destStoragePool, destVolumeInfo);
1875+
migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath, backingPath);
18751876
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
18761877
migrateDiskInfoList.add(migrateDiskInfo);
18771878
prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath());
@@ -1994,14 +1995,18 @@ protected String generateDestPath(Host destHost, StoragePoolVO destStoragePool,
19941995
return connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
19951996
}
19961997

1998+
protected String generateBackingPath(StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
1999+
return null;
2000+
}
2001+
19972002
/**
19982003
* Configures a {@link MigrateDiskInfo} object with disk type of BLOCK, Driver type RAW and Source DEV
19992004
*/
2000-
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath) {
2005+
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath, String backingPath) {
20012006
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
20022007
MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
20032008
MigrateCommand.MigrateDiskInfo.DriverType.RAW,
2004-
MigrateCommand.MigrateDiskInfo.Source.DEV, destPath);
2009+
MigrateCommand.MigrateDiskInfo.Source.DEV, destPath, backingPath);
20052010
}
20062011

20072012
/**
@@ -2023,7 +2028,7 @@ protected void copyTemplateToTargetFilesystemStorageIfNeeded(VolumeInfo srcVolum
20232028
/*
20242029
* Return backing file for volume (if any), only for KVM volumes
20252030
*/
2026-
private String getVolumeBackingFile(VolumeInfo srcVolumeInfo) {
2031+
String getVolumeBackingFile(VolumeInfo srcVolumeInfo) {
20272032
if (srcVolumeInfo.getHypervisorType() == HypervisorType.KVM &&
20282033
srcVolumeInfo.getTemplateId() != null && srcVolumeInfo.getPoolId() != null) {
20292034
VMTemplateVO template = _vmTemplateDao.findById(srcVolumeInfo.getTemplateId());

engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,27 @@ private String prepareTestGetTemplateUuid() {
240240
public void configureMigrateDiskInfoTest() {
241241
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
242242
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
243-
MigrateCommand.MigrateDiskInfo migrateDiskInfo = kvmNonManagedStorageDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
243+
MigrateCommand.MigrateDiskInfo migrateDiskInfo = kvmNonManagedStorageDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath", null);
244244
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, migrateDiskInfo.getDiskType());
245245
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, migrateDiskInfo.getDriverType());
246246
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, migrateDiskInfo.getSource());
247247
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
248248
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
249249
}
250250

251+
@Test
252+
public void configureMigrateDiskInfoWithBackingTest() {
253+
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
254+
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
255+
MigrateCommand.MigrateDiskInfo migrateDiskInfo = kvmNonManagedStorageDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath", "backingPath");
256+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, migrateDiskInfo.getDiskType());
257+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, migrateDiskInfo.getDriverType());
258+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, migrateDiskInfo.getSource());
259+
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
260+
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
261+
Assert.assertEquals("backingPath", migrateDiskInfo.getBackingStoreText());
262+
}
263+
251264
@Test
252265
public void shouldMigrateVolumeTest() {
253266
StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());

engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,27 @@ public void generateDestPathTest() {
192192
public void configureMigrateDiskInfoTest() {
193193
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
194194
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
195-
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
195+
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath", null);
196196
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
197197
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
198198
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
199199
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
200200
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
201201
}
202202

203+
@Test
204+
public void configureMigrateDiskInfoWithBackingTest() {
205+
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
206+
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
207+
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath", "backingPath");
208+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
209+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
210+
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
211+
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
212+
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
213+
Assert.assertEquals("backingPath", migrateDiskInfo.getBackingStoreText());
214+
}
215+
203216
@Test
204217
public void setVolumePathTest() {
205218
VolumeVO volume = new VolumeVO("name", 0l, 0l, 0l, 0l, 0l, "folder", "path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT);

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,16 @@ protected String replaceStorage(String xmlDesc, Map<String, MigrateCommand.Migra
575575
diskNode.appendChild(newChildSourceNode);
576576
} else if (migrateStorageManaged && "auth".equals(diskChildNode.getNodeName())) {
577577
diskNode.removeChild(diskChildNode);
578+
} else if ("backingStore".equals(diskChildNode.getNodeName()) && migrateDiskInfo.getBackingStoreText() != null) {
579+
for (int b = 0; b < diskChildNode.getChildNodes().getLength(); b++) {
580+
Node backingChild = diskChildNode.getChildNodes().item(b);
581+
if ("source".equals(backingChild.getNodeName())) {
582+
diskChildNode.removeChild(backingChild);
583+
Element newChildBackingElement = doc.createElement("source");
584+
newChildBackingElement.setAttribute(migrateDiskInfo.getSource().toString(), migrateDiskInfo.getBackingStoreText());
585+
diskChildNode.appendChild(newChildBackingElement);
586+
}
587+
}
578588
} else if ("encryption".equals(diskChildNode.getNodeName())) {
579589
for (int s = 0; s < diskChildNode.getChildNodes().getLength(); s++) {
580590
Node encryptionChild = diskChildNode.getChildNodes().item(s);

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,51 @@ public void testReplaceStorageWithSecrets() throws Exception {
794794
assertXpath(doc, "/domain/devices/disk/encryption/secret/@uuid", expectedSecretUuid);
795795
}
796796

797+
@Test
798+
public void testReplaceBackingStore() throws Exception {
799+
Map<String, MigrateDiskInfo> mapMigrateStorage = new HashMap<String, MigrateDiskInfo>();
800+
final String xmlDesc =
801+
"<domain type='kvm' id='3'>" +
802+
" <devices>" +
803+
" <disk type='file' device='disk'>\n" +
804+
" <driver name='qemu' type='qcow2' cache='none'/>\n" +
805+
" <source file='/mnt/07eb495b-5590-3877-9fb7-23c6e9a40d40/bf8621b3-027c-497d-963b-06319650f048'/>\n" +
806+
" <target dev='vdb' bus='virtio'/>\n" +
807+
" <serial>bf8621b3027c497d963b</serial>\n" +
808+
" <alias name='virtio-disk1'/>\n" +
809+
" <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>\n" +
810+
" <backingStore type='file' index='1'>\n" +
811+
" <format type='raw'/>\n" +
812+
" <source file='/mnt/07eb495b-5590-3877-9fb7-23c6e9a40d40/bb4d4df4-c004-11e5-94ed-5254001daa61'/>\n" +
813+
" <backingStore/>\n" +
814+
" </backingStore>\n" +
815+
" <encryption format='luks'>\n" +
816+
" <secret type='passphrase' uuid='5644d664-a238-3a9b-811c-961f609d29f4'/>\n" +
817+
" </encryption>\n" +
818+
" </disk>\n" +
819+
" </devices>" +
820+
"</domain>";
821+
822+
final String volumeFile = "3530f749-82fd-458e-9485-a357e6e541db";
823+
final String backingFile = "0bc745b6-f3d7-44a9-ad8e-68904b77e2ab";
824+
String newDiskPath = "/mnt/2d0435e1-99e0-4f1d-94c0-bee1f6f8b99e/" + volumeFile;
825+
String newBackingStorePath = "/mnt/2d0435e1-99e0-4f1d-94c0-bee1f6f8b99e/" + backingFile;
826+
MigrateDiskInfo diskInfo = new MigrateDiskInfo("123456", DiskType.BLOCK, DriverType.RAW, Source.FILE, newDiskPath, newBackingStorePath);
827+
mapMigrateStorage.put("/mnt/07eb495b-5590-3877-9fb7-23c6e9a40d40/bf8621b3-027c-497d-963b-06319650f048", diskInfo);
828+
829+
final String result = libvirtMigrateCmdWrapper.replaceStorage(xmlDesc, mapMigrateStorage, false);
830+
InputStream in = IOUtils.toInputStream(result);
831+
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
832+
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
833+
Document doc = docBuilder.parse(in);
834+
assertXpath(doc, "/domain/devices/disk/backingStore/source/@file", newBackingStorePath);
835+
assertXpath(doc, "/domain/devices/disk/source/@file", newDiskPath);
836+
assertXpath(doc, "/domain/devices/disk/serial", "bf8621b3027c497d963b");
837+
838+
final String expectedSecretUuid = LibvirtComputingResource.generateSecretUUIDFromString(volumeFile);
839+
assertXpath(doc, "/domain/devices/disk/encryption/secret/@uuid", expectedSecretUuid);
840+
}
841+
797842
public void testReplaceStorageXmlDiskNotManagedStorage() throws ParserConfigurationException, TransformerException, SAXException, IOException {
798843
final LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
799844
String destDisk1FileName = "XXXXXXXXXXXXXX";

0 commit comments

Comments
 (0)