From 40ef0604be74f5682f5ecbe8eb014ae9cb0ae8d3 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Fri, 9 Jun 2023 11:51:33 +0530 Subject: [PATCH] This feature allows the storage migration for ScaleIO volumes between the ScaleIO storage pools of the same and different ScaleIO storage clusters. Previously this operation was blocked and now we are allowing it from API/UI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The volume migration within the same storage cluster is performed using ScaleIO’s native V-Tree migration operation where the volume will be live migrated to another storage pool. The volume migration across the different storage cluster is performed using libvirt’s live blockcopy operation which will first copy the volume to the destination storage pool and after a successful copy, the source volume will be deleted. Upon any failures during the block copy, source volume will remain to continue and other blockcopy operations will be reverted. A document PR is added here apache/cloudstack-documentation#321 --- .../cloud/vm/VirtualMachineManagerImpl.java | 3 +- .../vm/VirtualMachineManagerImplTest.java | 27 + .../storage/volume/VolumeServiceImpl.java | 4 + .../kvm/resource/LibvirtConnection.java | 2 + .../LibvirtMigrateVolumeCommandWrapper.java | 231 +++++++- .../kvm/storage/KVMStorageProcessor.java | 9 +- ...ibvirtMigrateVolumeCommandWrapperTest.java | 388 +++++++++++++ .../client/ScaleIOGatewayClientImpl.java | 2 +- .../driver/ScaleIOPrimaryDataStoreDriver.java | 281 +++++++++- .../ScaleIOPrimaryDataStoreDriverTest.java | 530 ++++++++++++++++++ pom.xml | 2 +- .../cloud/storage/VolumeApiServiceImpl.java | 21 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 10 - .../plugins/scaleio/test_scaleio_volumes.py | 243 ++++++++ 14 files changed, 1715 insertions(+), 38 deletions(-) create mode 100644 plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapperTest.java create mode 100644 plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 1b3d914b27a7..19b1cd3a7476 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -212,6 +212,7 @@ import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Storage; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateVO; @@ -2904,7 +2905,7 @@ protected Map buildMapUsingUserInformation(VirtualMachinePr * */ protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) { - if (!currentPool.isManaged()) { + if (!currentPool.isManaged() || currentPool.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { return; } if (currentPool.getId() == targetPool.getId()) { diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index 742bb3dda897..6f40c5d3a3a6 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -32,6 +32,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.config.ConfigKey; @@ -46,6 +48,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import org.mockito.runners.MockitoJUnitRunner; import com.cloud.agent.AgentManager; @@ -68,6 +71,7 @@ import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolHostVO; @@ -373,9 +377,26 @@ public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentS Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId(); } + @Test + public void allowVolumeMigrationsForPowerFlexStorage() { + Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); + Mockito.doReturn(Storage.StoragePoolType.PowerFlex).when(storagePoolVoMock).getPoolType(); + + virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); + + Mockito.verify(storagePoolVoMock).isManaged(); + Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId(); + } + @Test public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolEqualsTargetPool() { Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); + // return any storage type except powerflex/scaleio + List values = Arrays.asList(Storage.StoragePoolType.values()); + when(storagePoolVoMock.getPoolType()).thenAnswer((Answer) invocation -> { + List filteredValues = values.stream().filter(v -> v != Storage.StoragePoolType.PowerFlex).collect(Collectors.toList()); + int randomIndex = new Random().nextInt(filteredValues.size()); + return filteredValues.get(randomIndex); }); virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, storagePoolVoMock); @@ -386,6 +407,12 @@ public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentS @Test(expected = CloudRuntimeException.class) public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolNotEqualsTargetPool() { Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); + // return any storage type except powerflex/scaleio + List values = Arrays.asList(Storage.StoragePoolType.values()); + when(storagePoolVoMock.getPoolType()).thenAnswer((Answer) invocation -> { + List filteredValues = values.stream().filter(v -> v != Storage.StoragePoolType.PowerFlex).collect(Collectors.toList()); + int randomIndex = new Random().nextInt(filteredValues.size()); + return filteredValues.get(randomIndex); }); virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); } diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 48de0eb016be..ffc12b98c849 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -1648,6 +1648,10 @@ protected VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePool po newVol.setPoolType(pool.getPoolType()); newVol.setLastPoolId(lastPoolId); newVol.setPodId(pool.getPodId()); + if (volume.getPassphraseId() != null) { + newVol.setPassphraseId(volume.getPassphraseId()); + newVol.setEncryptFormat(volume.getEncryptFormat()); + } return volDao.persist(newVol); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java index c70a72f399c7..0f8031e3aaa1 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java @@ -21,6 +21,7 @@ import org.apache.log4j.Logger; import org.libvirt.Connect; +import org.libvirt.Library; import org.libvirt.LibvirtException; import com.cloud.hypervisor.Hypervisor; @@ -44,6 +45,7 @@ static public Connect getConnection(String hypervisorURI) throws LibvirtExceptio if (conn == null) { s_logger.info("No existing libvirtd connection found. Opening a new one"); conn = new Connect(hypervisorURI, false); + Library.initEventLoop(); s_logger.debug("Successfully connected to libvirt at: " + hypervisorURI); s_connections.put(hypervisorURI, conn); } else { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java index 311eb670e99f..f00c2aca088e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java @@ -29,27 +29,254 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import com.cloud.storage.Storage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; import java.util.Map; import java.util.UUID; +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient; +import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.DomainBlockJobInfo; +import org.libvirt.DomainInfo; +import org.libvirt.TypedParameter; +import org.libvirt.TypedUlongParameter; +import org.libvirt.LibvirtException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; @ResourceWrapper(handles = MigrateVolumeCommand.class) -public final class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper { +public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper { private static final Logger LOGGER = Logger.getLogger(LibvirtMigrateVolumeCommandWrapper.class); @Override public Answer execute(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { + VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); + PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore(); + + MigrateVolumeAnswer answer; + if (srcPrimaryDataStore.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { + answer = migratePowerFlexVolume(command, libvirtComputingResource); + } else { + answer = migrateRegularVolume(command, libvirtComputingResource); + } + + return answer; + } + + protected MigrateVolumeAnswer migratePowerFlexVolume(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { + + // Source Details + VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); + String srcPath = srcVolumeObjectTO.getPath(); + final String srcVolumeId = ScaleIOUtil.getVolumePath(srcVolumeObjectTO.getPath()); + final String vmName = srcVolumeObjectTO.getVmName(); + + // Destination Details + VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData(); + String destPath = destVolumeObjectTO.getPath(); + final String destVolumeId = ScaleIOUtil.getVolumePath(destVolumeObjectTO.getPath()); + Map destDetails = command.getDestDetails(); + final String destSystemId = destDetails.get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID); + String destDiskLabel = null; + + final String destDiskFileName = ScaleIOUtil.DISK_NAME_PREFIX + destSystemId + "-" + destVolumeId; + final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + destDiskFileName; + + Domain dm = null; + try { + final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); + Connect conn = libvirtUtilitiesHelper.getConnection(); + dm = libvirtComputingResource.getDomain(conn, vmName); + if (dm == null) { + return new MigrateVolumeAnswer(command, false, "Migrate volume failed due to can not find vm: " + vmName, null); + } + + DomainInfo.DomainState domainState = dm.getInfo().state ; + if (domainState != DomainInfo.DomainState.VIR_DOMAIN_RUNNING) { + return new MigrateVolumeAnswer(command, false, "Migrate volume failed due to VM is not running: " + vmName + " with domainState = " + domainState, null); + } + + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + PrimaryDataStoreTO spool = (PrimaryDataStoreTO)destVolumeObjectTO.getDataStore(); + KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getPoolType(), spool.getUuid()); + pool.connectPhysicalDisk(destVolumeObjectTO.getPath(), null); + + String srcSecretUUID = null; + String destSecretUUID = null; + if (destVolumeObjectTO.getPassphrase() != null) { + srcSecretUUID = libvirtComputingResource.createLibvirtVolumeSecret(conn, srcVolumeObjectTO.getPath(), srcVolumeObjectTO.getPassphrase()); + destSecretUUID = libvirtComputingResource.createLibvirtVolumeSecret(conn, destVolumeObjectTO.getPath(), destVolumeObjectTO.getPassphrase()); + } + + String diskdef = generateDestinationDiskXML(dm, srcVolumeId, diskFilePath, destSecretUUID); + destDiskLabel = generateDestinationDiskLabel(diskdef); + + TypedUlongParameter parameter = new TypedUlongParameter("bandwidth", 0); + TypedParameter[] parameters = new TypedParameter[1]; + parameters[0] = parameter; + + dm.blockCopy(destDiskLabel, diskdef, parameters, Domain.BlockCopyFlags.REUSE_EXT); + LOGGER.info(String.format("Block copy has started for the volume %s : %s ", destDiskLabel, srcPath)); + + return checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, srcSecretUUID); + + } catch (Exception e) { + String msg = "Migrate volume failed due to " + e.toString(); + LOGGER.warn(msg, e); + if (destDiskLabel != null) { + try { + dm.blockJobAbort(destDiskLabel, Domain.BlockJobAbortFlags.ASYNC); + } catch (LibvirtException ex) { + LOGGER.error("Migrate volume failed while aborting the block job due to " + ex.getMessage()); + } + } + return new MigrateVolumeAnswer(command, false, msg, null); + } finally { + if (dm != null) { + try { + dm.free(); + } catch (LibvirtException l) { + LOGGER.trace("Ignoring libvirt error.", l); + }; + } + } + } + + protected MigrateVolumeAnswer checkBlockJobStatus(MigrateVolumeCommand command, Domain dm, String diskLabel, String srcPath, String destPath, LibvirtComputingResource libvirtComputingResource, Connect conn, String srcSecretUUID) throws LibvirtException { + int timeBetweenTries = 1000; // Try more frequently (every sec) and return early if disk is found + int waitTimeInSec = command.getWait(); + while (waitTimeInSec > 0) { + DomainBlockJobInfo blockJobInfo = dm.getBlockJobInfo(diskLabel, 0); + if (blockJobInfo != null) { + LOGGER.debug(String.format("Volume %s : %s block copy progress: %s%% current value:%s end value:%s", diskLabel, srcPath, (blockJobInfo.end == 0)? 0 : 100*(blockJobInfo.cur / (double) blockJobInfo.end), blockJobInfo.cur, blockJobInfo.end)); + if (blockJobInfo.cur == blockJobInfo.end) { + LOGGER.info(String.format("Block copy completed for the volume %s : %s", diskLabel, srcPath)); + dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.PIVOT); + if (StringUtils.isNotEmpty(srcSecretUUID)) { + libvirtComputingResource.removeLibvirtVolumeSecret(conn, srcSecretUUID); + } + break; + } + } else { + LOGGER.info("Failed to get the block copy status, trying to abort the job"); + dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.ASYNC); + } + waitTimeInSec--; + + try { + Thread.sleep(timeBetweenTries); + } catch (Exception ex) { + // don't do anything + } + } + + if (waitTimeInSec <= 0) { + String msg = "Block copy is taking long time, failing the job"; + LOGGER.error(msg); + try { + dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.ASYNC); + } catch (LibvirtException ex) { + LOGGER.error("Migrate volume failed while aborting the block job due to " + ex.getMessage()); + } + return new MigrateVolumeAnswer(command, false, msg, null); + } + + return new MigrateVolumeAnswer(command, true, null, destPath); + } + + private String generateDestinationDiskLabel(String diskXml) throws ParserConfigurationException, IOException, SAXException { + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(new ByteArrayInputStream(diskXml.getBytes("UTF-8"))); + doc.getDocumentElement().normalize(); + + Element disk = doc.getDocumentElement(); + String diskLabel = getAttrValue("target", "dev", disk); + + return diskLabel; + } + + protected String generateDestinationDiskXML(Domain dm, String srcVolumeId, String diskFilePath, String destSecretUUID) throws LibvirtException, ParserConfigurationException, IOException, TransformerException, SAXException { + final String domXml = dm.getXMLDesc(0); + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(new ByteArrayInputStream(domXml.getBytes("UTF-8"))); + doc.getDocumentElement().normalize(); + + NodeList disks = doc.getElementsByTagName("disk"); + + for (int i = 0; i < disks.getLength(); i++) { + Element disk = (Element)disks.item(i); + String type = disk.getAttribute("type"); + if (!type.equalsIgnoreCase("network")) { + String diskDev = getAttrValue("source", "dev", disk); + if (StringUtils.isNotEmpty(diskDev) && diskDev.contains(srcVolumeId)) { + setAttrValue("source", "dev", diskFilePath, disk); + if (StringUtils.isNotEmpty(destSecretUUID)) { + setAttrValue("secret", "uuid", destSecretUUID, disk); + } + StringWriter diskSection = new StringWriter(); + Transformer xformer = TransformerFactory.newInstance().newTransformer(); + xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + xformer.transform(new DOMSource(disk), new StreamResult(diskSection)); + + return diskSection.toString(); + } + } + } + + return null; + } + + private static String getAttrValue(String tag, String attr, Element eElement) { + NodeList tagNode = eElement.getElementsByTagName(tag); + if (tagNode.getLength() == 0) { + return null; + } + Element node = (Element)tagNode.item(0); + return node.getAttribute(attr); + } + + private static void setAttrValue(String tag, String attr, String newValue, Element eElement) { + NodeList tagNode = eElement.getElementsByTagName(tag); + if (tagNode.getLength() == 0) { + return; + } + Element node = (Element)tagNode.item(0); + node.setAttribute(attr, newValue); + } + + protected MigrateVolumeAnswer migrateRegularVolume(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { KVMStoragePoolManager storagePoolManager = libvirtComputingResource.getStoragePoolMgr(); VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore(); Map srcDetails = command.getSrcDetails(); - String srcPath = srcDetails != null ? srcDetails.get(DiskTO.IQN) : srcVolumeObjectTO.getPath(); VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData(); 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 cae872e287f2..a8a7d6f56944 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 @@ -37,6 +37,7 @@ import javax.naming.ConfigurationException; import com.cloud.storage.ScopeType; +import com.cloud.storage.Volume; import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand; @@ -2448,7 +2449,12 @@ public Answer copyVolumeFromPrimaryToPrimary(CopyCommand cmd) { destPool = storagePoolMgr.getStoragePool(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid()); try { - storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds()); + if (srcVol.getPassphrase() != null && srcVol.getVolumeType().equals(Volume.Type.ROOT)) { + volume.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS); + storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds(), srcVol.getPassphrase(), destVol.getPassphrase(), srcVol.getProvisioningType()); + } else { + storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds()); + } } catch (Exception e) { // Any exceptions while copying the disk, should send failed answer with the error message String errMsg = String.format("Failed to copy volume: %s to dest storage: %s, due to %s", srcVol.getName(), destPrimaryStore.getName(), e.toString()); s_logger.debug(errMsg, e); @@ -2467,6 +2473,7 @@ public Answer copyVolumeFromPrimaryToPrimary(CopyCommand cmd) { String path = destPrimaryStore.isManaged() ? destVolumeName : destVolumePath + File.separator + destVolumeName; newVol.setPath(path); newVol.setFormat(destFormat); + newVol.setEncryptFormat(destVol.getEncryptFormat()); return new CopyCmdAnswer(newVol); } catch (final CloudRuntimeException e) { s_logger.debug("Failed to copyVolumeFromPrimaryToPrimary: ", e); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapperTest.java new file mode 100644 index 000000000000..c278144b4e1c --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapperTest.java @@ -0,0 +1,388 @@ +// +// 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.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.storage.MigrateVolumeCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.storage.Storage; +import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.DomainBlockJobInfo; +import org.libvirt.DomainInfo; +import org.libvirt.LibvirtException; +import org.libvirt.TypedParameter; +import org.mockito.InjectMocks; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtMigrateVolumeCommandWrapperTest { + + @Spy + @InjectMocks + private LibvirtMigrateVolumeCommandWrapper libvirtMigrateVolumeCommandWrapper; + + @Mock + MigrateVolumeCommand command; + + @Mock + LibvirtComputingResource libvirtComputingResource; + + @Mock + LibvirtUtilitiesHelper libvirtUtilitiesHelper; + + private String domxml = "\n" + + " i-2-27-VM\n" + + " 2d37fe1a-621a-4903-9ab5-5c9544c733f8\n" + + " Ubuntu 18.04 LTS\n" + + " 524288\n" + + " 524288\n" + + " 1\n" + + " \n" + + " 256\n" + + " \n" + + " \n" + + " /machine\n" + + " \n" + + " \n" + + " \n" + + " Apache Software Foundation\n" + + " CloudStack KVM Hypervisor\n" + + " 2d37fe1a-621a-4903-9ab5-5c9544c733f8\n" + + " \n" + + " \n" + + " \n" + + " hvm\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " qemu64\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " destroy\n" + + " restart\n" + + " destroy\n" + + " \n" + + " /usr/libexec/qemu-kvm\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 38a54bf719f24af6b070\n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0ceeb7c643b447aba5ce\n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "