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

harikrishna 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 40cc10a73de Allow volume migrations in ScaleIO within and across 
ScaleIO storage clusters (#7408)
40cc10a73de is described below

commit 40cc10a73deb3211d76caa70f7f3b66e450b3214
Author: Harikrishna <[email protected]>
AuthorDate: Wed Jun 21 11:57:05 2023 +0530

    Allow volume migrations in ScaleIO within and across ScaleIO storage 
clusters (#7408)
    
    * Live storage migration of volume in scaleIO within same storage scaleio 
cluster
    
    * Added migrate command
    
    * Recent changes of migration across clusters
    
    * Fixed uuid
    
    * recent changes
    
    * Pivot changes
    
    * working blockcopy api in libvirt
    
    * Checking block copy status
    
    * Formatting code
    
    * Fixed failures
    
    * code refactoring and some changes
    
    * Removed unused methods
    
    * removed unused imports
    
    * Unit tests to check if volume belongs to same or different storage 
scaleio cluster
    
    * Unit tests for volume livemigration in ScaleIOPrimaryDataStoreDriver
    
    * Fixed offline volume migration case and allowed encrypted volume migration
    
    * Added more integration tests
    
    * Support for migration of encrypted volumes across different scaleio 
clusters
    
    * Fix UI notifications for migrate volume
    
    * Data volume offline migration: save encryption details to destination 
volume entry
    
    * Offline storage migration for scaleio encrypted volumes
    
    * Allow multiple Volumes to be migrated with 
migrateVirtualMachineWithVolume API
    
    * Removed unused unittests
    
    * Removed duplicate keys in migrate volume vue file
    
    * Fix Unit tests
    
    * Add volume secrets if does not exists during volume migrations. secrets 
are getting cleared on package upgrades.
    
    * Fix secret UUID for encrypted volume migration
    
    * Added a null check for secret before removing
    
    * Added more unit tests
    
    * Fixed passphrase check
    
    * Add image options to the encypted volume conversion
---
 .../com/cloud/vm/VirtualMachineManagerImpl.java    |   3 +-
 .../cloud/vm/VirtualMachineManagerImplTest.java    |  27 ++
 .../storage/volume/VolumeServiceImpl.java          |   4 +
 .../hypervisor/kvm/resource/LibvirtConnection.java |   2 +
 .../LibvirtMigrateVolumeCommandWrapper.java        | 232 ++++++++-
 .../kvm/storage/KVMStorageProcessor.java           |   9 +-
 .../kvm/storage/ScaleIOStorageAdaptor.java         |   1 +
 .../cloudstack/utils/qemu/QemuImageOptions.java    |  11 +-
 .../LibvirtMigrateVolumeCommandWrapperTest.java    | 388 +++++++++++++++
 .../datastore/client/ScaleIOGatewayClientImpl.java |   2 +-
 .../driver/ScaleIOPrimaryDataStoreDriver.java      | 281 ++++++++++-
 .../driver/ScaleIOPrimaryDataStoreDriverTest.java  | 527 +++++++++++++++++++++
 .../com/cloud/storage/VolumeApiServiceImpl.java    |  21 +-
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  |  10 -
 .../plugins/scaleio/test_scaleio_volumes.py        | 243 ++++++++++
 15 files changed, 1722 insertions(+), 39 deletions(-)

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 db76fec9b86..9b37132d07c 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.service.dao.ServiceOfferingDao;
 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;
@@ -2925,7 +2926,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
      *  </ul>
      */
     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 c4be16ea255..15a2f2c0ac1 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.Arrays;
 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.InjectMocks;
 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.ServiceOfferingVO;
 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 class VirtualMachineManagerImplTest {
         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<Storage.StoragePoolType> values = 
Arrays.asList(Storage.StoragePoolType.values());
+        
when(storagePoolVoMock.getPoolType()).thenAnswer((Answer<Storage.StoragePoolType>)
 invocation -> {
+            List<Storage.StoragePoolType> 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 class VirtualMachineManagerImplTest {
     @Test(expected = CloudRuntimeException.class)
     public void 
executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolNotEqualsTargetPool()
 {
         Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
+        // return any storage type except powerflex/scaleio
+        List<Storage.StoragePoolType> values = 
Arrays.asList(Storage.StoragePoolType.values());
+        
when(storagePoolVoMock.getPoolType()).thenAnswer((Answer<Storage.StoragePoolType>)
 invocation -> {
+            List<Storage.StoragePoolType> 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 48de0eb016b..ffc12b98c84 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 @@ public class VolumeServiceImpl implements VolumeService {
         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 c70a72f399c..0f8031e3aaa 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 java.util.Map;
 
 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 @@ public class LibvirtConnection {
         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 311eb670e99..5c893e5d12f 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,255 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
 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.ArrayUtils;
+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<MigrateVolumeCommand, Answer, LibvirtComputingResource> {
+public class LibvirtMigrateVolumeCommandWrapper extends 
CommandWrapper<MigrateVolumeCommand, Answer, LibvirtComputingResource> {
     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<String, String> 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 (ArrayUtils.isNotEmpty(destVolumeObjectTO.getPassphrase())) {
+                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<String, String> 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 cae872e287f..a8a7d6f5694 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 java.util.UUID;
 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 class KVMStorageProcessor implements 
StorageProcessor {
 
             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 class KVMStorageProcessor implements 
StorageProcessor {
             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/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
index 09c7e146e49..607dd620f61 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
@@ -387,6 +387,7 @@ public class ScaleIOStorageAdaptor implements 
StorageAdaptor {
 
             boolean forceSourceFormat = srcQemuFile.getFormat() == 
QemuImg.PhysicalDiskFormat.RAW;
             LOGGER.debug(String.format("Starting copy from source disk %s(%s) 
to PowerFlex volume %s(%s), forcing source format is %b", 
srcQemuFile.getFileName(), srcQemuFile.getFormat(), destQemuFile.getFileName(), 
destQemuFile.getFormat(), forceSourceFormat));
+            qemuImageOpts.setImageOptsFlag(true);
             qemu.convert(srcQemuFile, destQemuFile, options, qemuObjects, 
qemuImageOpts,null, forceSourceFormat);
             LOGGER.debug("Successfully converted source disk image " + 
srcQemuFile.getFileName() + " to PowerFlex volume: " + destDisk.getPath());
 
diff --git 
a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
 
b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
index f9a2e4ba652..4a577ef3400 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
@@ -30,6 +30,7 @@ public class QemuImageOptions {
     private static final String LUKS_KEY_SECRET_PARAM_KEY = "key-secret";
     private static final String QCOW2_KEY_SECRET_PARAM_KEY = 
"encrypt.key-secret";
     private static final String DRIVER = "driver";
+    private boolean addImageOpts = false;
 
     private QemuImg.PhysicalDiskFormat format;
     private static final List<QemuImg.PhysicalDiskFormat> 
DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS = 
Arrays.asList(QemuImg.PhysicalDiskFormat.QCOW2, 
QemuImg.PhysicalDiskFormat.LUKS);
@@ -71,13 +72,19 @@ public class QemuImageOptions {
         }
     }
 
+    public void setImageOptsFlag(boolean addImageOpts) {
+        this.addImageOpts = addImageOpts;
+    }
+
     /**
      * Converts QemuImageOptions into the command strings required by qemu-img 
flags
      * @return array of strings representing command flag and value 
(--image-opts)
      */
     public String[] toCommandFlag() {
-        if (format == null || 
!DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS.contains(format)) {
-            return new String[] { params.get(FILENAME_PARAM_KEY) };
+        if (!addImageOpts) {
+            if (format == null || 
!DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS.contains(format)) {
+                return new String[] { params.get(FILENAME_PARAM_KEY) };
+            }
         }
         Map<String, String> sorted = new TreeMap<>(params);
         String paramString = 
Joiner.on(",").withKeyValueSeparator("=").join(sorted);
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 00000000000..c278144b4e1
--- /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 = "<domain type='kvm' id='1'>\n" +
+            "  <name>i-2-27-VM</name>\n" +
+            "  <uuid>2d37fe1a-621a-4903-9ab5-5c9544c733f8</uuid>\n" +
+            "  <description>Ubuntu 18.04 LTS</description>\n" +
+            "  <memory unit='KiB'>524288</memory>\n" +
+            "  <currentMemory unit='KiB'>524288</currentMemory>\n" +
+            "  <vcpu placement='static'>1</vcpu>\n" +
+            "  <cputune>\n" +
+            "    <shares>256</shares>\n" +
+            "  </cputune>\n" +
+            "  <resource>\n" +
+            "    <partition>/machine</partition>\n" +
+            "  </resource>\n" +
+            "  <sysinfo type='smbios'>\n" +
+            "    <system>\n" +
+            "      <entry name='manufacturer'>Apache Software 
Foundation</entry>\n" +
+            "      <entry name='product'>CloudStack KVM Hypervisor</entry>\n" +
+            "      <entry 
name='uuid'>2d37fe1a-621a-4903-9ab5-5c9544c733f8</entry>\n" +
+            "    </system>\n" +
+            "  </sysinfo>\n" +
+            "  <os>\n" +
+            "    <type arch='x86_64' 
machine='pc-i440fx-rhel7.6.0'>hvm</type>\n" +
+            "    <boot dev='cdrom'/>\n" +
+            "    <boot dev='hd'/>\n" +
+            "    <smbios mode='sysinfo'/>\n" +
+            "  </os>\n" +
+            "  <features>\n" +
+            "    <acpi/>\n" +
+            "    <apic/>\n" +
+            "    <pae/>\n" +
+            "  </features>\n" +
+            "  <cpu mode='custom' match='exact' check='full'>\n" +
+            "    <model fallback='forbid'>qemu64</model>\n" +
+            "    <feature policy='require' name='x2apic'/>\n" +
+            "    <feature policy='require' name='hypervisor'/>\n" +
+            "    <feature policy='require' name='lahf_lm'/>\n" +
+            "    <feature policy='disable' name='svm'/>\n" +
+            "  </cpu>\n" +
+            "  <clock offset='utc'>\n" +
+            "    <timer name='kvmclock'/>\n" +
+            "  </clock>\n" +
+            "  <on_poweroff>destroy</on_poweroff>\n" +
+            "  <on_reboot>restart</on_reboot>\n" +
+            "  <on_crash>destroy</on_crash>\n" +
+            "  <devices>\n" +
+            "    <emulator>/usr/libexec/qemu-kvm</emulator>\n" +
+            "    <disk type='block' device='disk'>\n" +
+            "      <driver name='qemu' type='raw' cache='none'/>\n" +
+            "      <source 
dev='/dev/disk/by-id/emc-vol-610204d03e3ad60f-bec108c400000018' index='4'/>\n" +
+            "      <backingStore/>\n" +
+            "      <target dev='vda' bus='virtio'/>\n" +
+            "      <serial>38a54bf719f24af6b070</serial>\n" +
+            "      <alias name='virtio-disk0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' 
function='0x0'/>\n" +
+            "    </disk>\n" +
+            "    <disk type='block' device='disk'>\n" +
+            "      <driver name='qemu' type='raw' cache='none'/>\n" +
+            "      <source 
dev='/dev/disk/by-id/emc-vol-7332760565f6340f-01b381820000001c' index='2'/>\n" +
+            "      <backingStore/>\n" +
+            "      <target dev='vdb' bus='virtio'/>\n" +
+            "      <serial>0ceeb7c643b447aba5ce</serial>\n" +
+            "      <alias name='virtio-disk1'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' 
function='0x0'/>\n" +
+            "    </disk>\n" +
+            "    <disk type='file' device='cdrom'>\n" +
+            "      <driver name='qemu'/>\n" +
+            "      <target dev='hdc' bus='ide'/>\n" +
+            "      <readonly/>\n" +
+            "      <alias name='ide0-1-0'/>\n" +
+            "      <address type='drive' controller='0' bus='1' target='0' 
unit='0'/>\n" +
+            "    </disk>\n" +
+            "    <controller type='usb' index='0' model='piix3-uhci'>\n" +
+            "      <alias name='usb'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' 
function='0x2'/>\n" +
+            "    </controller>\n" +
+            "    <controller type='pci' index='0' model='pci-root'>\n" +
+            "      <alias name='pci.0'/>\n" +
+            "    </controller>\n" +
+            "    <controller type='ide' index='0'>\n" +
+            "      <alias name='ide'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' 
function='0x1'/>\n" +
+            "    </controller>\n" +
+            "    <controller type='virtio-serial' index='0'>\n" +
+            "      <alias name='virtio-serial0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' 
function='0x0'/>\n" +
+            "    </controller>\n" +
+            "    <interface type='bridge'>\n" +
+            "      <mac address='02:00:23:fd:00:17'/>\n" +
+            "      <source bridge='breth1-1640'/>\n" +
+            "      <bandwidth>\n" +
+            "        <inbound average='25600' peak='25600'/>\n" +
+            "        <outbound average='25600' peak='25600'/>\n" +
+            "      </bandwidth>\n" +
+            "      <target dev='vnet0'/>\n" +
+            "      <model type='virtio'/>\n" +
+            "      <link state='up'/>\n" +
+            "      <alias name='net0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' 
function='0x0'/>\n" +
+            "    </interface>\n" +
+            "    <serial type='pty'>\n" +
+            "      <source path='/dev/pts/1'/>\n" +
+            "      <target type='isa-serial' port='0'>\n" +
+            "        <model name='isa-serial'/>\n" +
+            "      </target>\n" +
+            "      <alias name='serial0'/>\n" +
+            "    </serial>\n" +
+            "    <console type='pty' tty='/dev/pts/1'>\n" +
+            "      <source path='/dev/pts/1'/>\n" +
+            "      <target type='serial' port='0'/>\n" +
+            "      <alias name='serial0'/>\n" +
+            "    </console>\n" +
+            "    <channel type='unix'>\n" +
+            "      <source mode='bind' 
path='/var/lib/libvirt/qemu/i-2-27-VM.org.qemu.guest_agent.0'/>\n" +
+            "      <target type='virtio' name='org.qemu.guest_agent.0' 
state='connected'/>\n" +
+            "      <alias name='channel0'/>\n" +
+            "      <address type='virtio-serial' controller='0' bus='0' 
port='1'/>\n" +
+            "    </channel>\n" +
+            "    <input type='tablet' bus='usb'>\n" +
+            "      <alias name='input0'/>\n" +
+            "      <address type='usb' bus='0' port='1'/>\n" +
+            "    </input>\n" +
+            "    <input type='mouse' bus='ps2'>\n" +
+            "      <alias name='input1'/>\n" +
+            "    </input>\n" +
+            "    <input type='keyboard' bus='ps2'>\n" +
+            "      <alias name='input2'/>\n" +
+            "    </input>\n" +
+            "    <graphics type='vnc' port='5900' autoport='yes' 
listen='10.0.32.170'>\n" +
+            "      <listen type='address' address='10.0.32.170'/>\n" +
+            "    </graphics>\n" +
+            "    <audio id='1' type='none'/>\n" +
+            "    <video>\n" +
+            "      <model type='cirrus' vram='16384' heads='1' 
primary='yes'/>\n" +
+            "      <alias name='video0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' 
function='0x0'/>\n" +
+            "    </video>\n" +
+            "    <watchdog model='i6300esb' action='none'>\n" +
+            "      <alias name='watchdog0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x08' 
function='0x0'/>\n" +
+            "    </watchdog>\n" +
+            "    <memballoon model='virtio'>\n" +
+            "      <alias name='balloon0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' 
function='0x0'/>\n" +
+            "    </memballoon>\n" +
+            "  </devices>\n" +
+            "  <seclabel type='dynamic' model='dac' relabel='yes'>\n" +
+            "    <label>+0:+0</label>\n" +
+            "    <imagelabel>+0:+0</imagelabel>\n" +
+            "  </seclabel>\n" +
+            "</domain>\n";
+
+    @Test
+    public void testPowerFlexMigrateVolumeMethod() {
+        VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
+        Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
+
+        PrimaryDataStoreTO srcPrimaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
Mockito.doReturn(srcPrimaryDataStore).when(srcVolumeObjectTO).getDataStore();
+        
Mockito.doReturn(Storage.StoragePoolType.PowerFlex).when(srcPrimaryDataStore).getPoolType();
+
+        MigrateVolumeAnswer powerFlexAnswer = 
Mockito.mock(MigrateVolumeAnswer.class);
+        MigrateVolumeAnswer regularVolumeAnswer = 
Mockito.mock(MigrateVolumeAnswer.class);
+
+        Mockito.doReturn(true).when(powerFlexAnswer).getResult();
+        
Mockito.doReturn(powerFlexAnswer).when(libvirtMigrateVolumeCommandWrapper).migratePowerFlexVolume(command,
 libvirtComputingResource);
+
+        Answer answer = libvirtMigrateVolumeCommandWrapper.execute(command, 
libvirtComputingResource);
+
+        Assert.assertTrue(answer.getResult());
+    }
+
+    @Test
+    public void testRegularMigrateVolumeMethod() {
+        VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
+        Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
+
+        PrimaryDataStoreTO srcPrimaryDataStore = 
Mockito.mock(PrimaryDataStoreTO.class);
+        
Mockito.doReturn(srcPrimaryDataStore).when(srcVolumeObjectTO).getDataStore();
+        
Mockito.doReturn(Storage.StoragePoolType.NetworkFilesystem).when(srcPrimaryDataStore).getPoolType();
+
+        MigrateVolumeAnswer powerFlexAnswer = 
Mockito.mock(MigrateVolumeAnswer.class);
+        MigrateVolumeAnswer regularVolumeAnswer = 
Mockito.mock(MigrateVolumeAnswer.class);
+
+        Mockito.doReturn(false).when(regularVolumeAnswer).getResult();
+        
Mockito.doReturn(regularVolumeAnswer).when(libvirtMigrateVolumeCommandWrapper).migrateRegularVolume(command,
 libvirtComputingResource);
+
+        Answer answer = libvirtMigrateVolumeCommandWrapper.execute(command, 
libvirtComputingResource);
+
+        Assert.assertFalse(answer.getResult());
+    }
+
+    @Test
+    public void testMigratePowerFlexVolume() throws LibvirtException, 
ParserConfigurationException, IOException, TransformerException, SAXException {
+        VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
+        Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
+        String srcPath = "bec108c400000018:vol-60-7acb-9e22";
+        Mockito.doReturn(srcPath).when(srcVolumeObjectTO).getPath();
+        String vmName = "i-2-27-VM";
+        Mockito.doReturn(vmName).when(srcVolumeObjectTO).getVmName();
+
+        VolumeObjectTO destVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
+        Mockito.doReturn(destVolumeObjectTO).when(command).getDestData();
+        String destPath = "01b381820000001c:vol-60-ec76-b7dc";
+        Mockito.doReturn(destPath).when(destVolumeObjectTO).getPath();
+        Map<String, String> destDetails = new HashMap<>();
+        destDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, 
"610204d03e3ad60f");
+
+        
Mockito.doReturn(libvirtUtilitiesHelper).when(libvirtComputingResource).getLibvirtUtilitiesHelper();
+        Connect conn = Mockito.mock(Connect.class);
+        Domain dm = Mockito.mock(Domain.class);
+        Mockito.doReturn(conn).when(libvirtUtilitiesHelper).getConnection();
+        Mockito.doReturn(dm).when(libvirtComputingResource).getDomain(conn, 
vmName);
+
+        DomainInfo domainInfo = Mockito.mock(DomainInfo.class);
+        domainInfo.state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING;
+        Mockito.doReturn(domainInfo).when(dm).getInfo();
+
+        KVMStoragePoolManager storagePoolMgr = 
Mockito.mock(KVMStoragePoolManager.class);
+        
Mockito.doReturn(storagePoolMgr).when(libvirtComputingResource).getStoragePoolMgr();
+        PrimaryDataStoreTO spool = Mockito.mock(PrimaryDataStoreTO.class);
+        Mockito.doReturn(spool).when(destVolumeObjectTO).getDataStore();
+        KVMStoragePool pool = Mockito.mock(KVMStoragePool.class);
+        
Mockito.doReturn(pool).when(storagePoolMgr).getStoragePool(Mockito.any(), 
Mockito.any());
+        Mockito.doReturn(true).when(pool).connectPhysicalDisk(Mockito.any(), 
Mockito.any());
+
+        Mockito.doReturn(null).when(destVolumeObjectTO).getPassphrase();
+
+        Mockito.doReturn(domxml).when(dm).getXMLDesc(0);
+
+        Mockito.doNothing().when(dm).blockCopy(Matchers.anyString(), 
Matchers.anyString(), Matchers.any(TypedParameter[].class), Matchers.anyInt());
+        MigrateVolumeAnswer answer = new MigrateVolumeAnswer(command, true, 
null, destPath);
+        
Mockito.doReturn(answer).when(libvirtMigrateVolumeCommandWrapper).checkBlockJobStatus(Mockito.any(),
 Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+
+        Answer migrateVolumeAnswer = 
libvirtMigrateVolumeCommandWrapper.migratePowerFlexVolume(command, 
libvirtComputingResource);
+
+        Assert.assertTrue(migrateVolumeAnswer.getResult());
+    }
+
+    @Test
+    public void testMigratePowerFlexVolumeFailure() throws LibvirtException, 
ParserConfigurationException, IOException, TransformerException, SAXException {
+        VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
+        Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
+        String srcPath = "bec108c400000018:vol-60-7acb-9e22";
+        Mockito.doReturn(srcPath).when(srcVolumeObjectTO).getPath();
+        String vmName = "i-2-27-VM";
+        Mockito.doReturn(vmName).when(srcVolumeObjectTO).getVmName();
+
+        VolumeObjectTO destVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
+        Mockito.doReturn(destVolumeObjectTO).when(command).getDestData();
+        String destPath = "01b381820000001c:vol-60-ec76-b7dc";
+        Mockito.doReturn(destPath).when(destVolumeObjectTO).getPath();
+        Map<String, String> destDetails = new HashMap<>();
+        destDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, 
"610204d03e3ad60f");
+
+        
Mockito.doReturn(libvirtUtilitiesHelper).when(libvirtComputingResource).getLibvirtUtilitiesHelper();
+        Connect conn = Mockito.mock(Connect.class);
+        Domain dm = Mockito.mock(Domain.class);
+        Mockito.doReturn(conn).when(libvirtUtilitiesHelper).getConnection();
+        Mockito.doReturn(dm).when(libvirtComputingResource).getDomain(conn, 
vmName);
+
+        DomainInfo domainInfo = Mockito.mock(DomainInfo.class);
+        domainInfo.state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING;
+        Mockito.doReturn(domainInfo).when(dm).getInfo();
+
+        KVMStoragePoolManager storagePoolMgr = 
Mockito.mock(KVMStoragePoolManager.class);
+        
Mockito.doReturn(storagePoolMgr).when(libvirtComputingResource).getStoragePoolMgr();
+        PrimaryDataStoreTO spool = Mockito.mock(PrimaryDataStoreTO.class);
+        Mockito.doReturn(spool).when(destVolumeObjectTO).getDataStore();
+        KVMStoragePool pool = Mockito.mock(KVMStoragePool.class);
+        
Mockito.doReturn(pool).when(storagePoolMgr).getStoragePool(Mockito.any(), 
Mockito.any());
+        Mockito.doReturn(true).when(pool).connectPhysicalDisk(Mockito.any(), 
Mockito.any());
+
+        Mockito.doReturn(null).when(destVolumeObjectTO).getPassphrase();
+        Mockito.doReturn(domxml).when(dm).getXMLDesc(0);
+        
Mockito.doThrow(LibvirtException.class).when(dm).blockCopy(Matchers.anyString(),
 Matchers.anyString(), Matchers.any(TypedParameter[].class), Matchers.anyInt());
+
+        Answer migrateVolumeAnswer = 
libvirtMigrateVolumeCommandWrapper.migratePowerFlexVolume(command, 
libvirtComputingResource);
+
+        Assert.assertFalse(migrateVolumeAnswer.getResult());
+    }
+
+    @Test
+    public void testCheckBlockJobStatus() throws LibvirtException {
+        Connect conn = Mockito.mock(Connect.class);
+        Domain dm = Mockito.mock(Domain.class);
+        String destDiskLabel = "vda";
+        String srcPath = "bec108c400000018:vol-60-7acb-9e22";
+        String destPath = "01b381820000001c:vol-60-ec76-b7dc";
+        Mockito.doReturn(60).when(command).getWait();
+        DomainBlockJobInfo blockJobInfo = 
Mockito.mock(DomainBlockJobInfo.class);
+        Mockito.doReturn(blockJobInfo).when(dm).getBlockJobInfo(destDiskLabel, 
0);
+        blockJobInfo.cur = 100;
+        blockJobInfo.end = 100;
+
+        MigrateVolumeAnswer answer = 
libvirtMigrateVolumeCommandWrapper.checkBlockJobStatus(command, dm, 
destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, null);
+
+        Assert.assertTrue(answer.getResult());
+    }
+
+    @Test
+    public void testCheckBlockJobStatusFailure() throws LibvirtException {
+        Connect conn = Mockito.mock(Connect.class);
+        Domain dm = Mockito.mock(Domain.class);
+        String destDiskLabel = "vda";
+        String srcPath = "bec108c400000018:vol-60-7acb-9e22";
+        String destPath = "01b381820000001c:vol-60-ec76-b7dc";
+        Mockito.doReturn(1).when(command).getWait();
+        DomainBlockJobInfo blockJobInfo = 
Mockito.mock(DomainBlockJobInfo.class);
+        Mockito.doReturn(blockJobInfo).when(dm).getBlockJobInfo(destDiskLabel, 
0);
+        blockJobInfo.cur = 10;
+        blockJobInfo.end = 100;
+
+        MigrateVolumeAnswer answer = 
libvirtMigrateVolumeCommandWrapper.checkBlockJobStatus(command, dm, 
destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, null);
+
+        Assert.assertFalse(answer.getResult());
+    }
+
+}
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java
index ad1279b5221..61e190d5239 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java
@@ -761,7 +761,7 @@ public class ScaleIOGatewayClientImpl implements 
ScaleIOGatewayClient {
             }
 
             String srcPoolId = volume.getStoragePoolId();
-            LOG.debug("Migrating the volume: " + srcVolumeId + " on the src 
pool: " + srcPoolId + " to the dest pool: " + destPoolId +
+            LOG.info("Migrating the volume: " + srcVolumeId + " on the src 
pool: " + srcPoolId + " to the dest pool: " + destPoolId +
                     " in the same PowerFlex cluster");
 
             post("/instances/Volume::" + srcVolumeId + "/action/migrateVTree",
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
index f15fb1c296e..3a285b49904 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
@@ -22,6 +22,7 @@ import java.util.Map;
 
 import javax.inject.Inject;
 
+import com.cloud.agent.api.storage.MigrateVolumeCommand;
 import com.cloud.agent.api.storage.ResizeVolumeCommand;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.host.HostVO;
@@ -41,6 +42,7 @@ import 
org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.storage.RemoteHostEndPoint;
@@ -70,6 +72,7 @@ import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.to.DataObjectType;
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
 import com.cloud.alert.AlertManager;
 import com.cloud.configuration.Config;
 import com.cloud.host.Host;
@@ -126,12 +129,14 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
     private HostDao hostDao;
     @Inject
     private VMInstanceDao vmInstanceDao;
+    @Inject
+    private VolumeService volumeService;
 
     public ScaleIOPrimaryDataStoreDriver() {
 
     }
 
-    private ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) 
throws Exception {
+    public ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) 
throws Exception {
         return 
ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePoolId, 
storagePoolDetailsDao);
     }
 
@@ -253,12 +258,33 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         }
     }
 
+    public void revokeVolumeAccess(String volumePath, Host host, DataStore 
dataStore) {
+        if (host == null) {
+            LOGGER.warn("Declining to revoke access to PowerFlex volume when a 
host is not provided");
+            return;
+        }
+
+        try {
+            LOGGER.debug("Revoking access for PowerFlex volume: " + 
volumePath);
+
+            final String sdcId = getConnectedSdc(dataStore.getId(), 
host.getId());
+            if (StringUtils.isBlank(sdcId)) {
+                throw new CloudRuntimeException("Unable to revoke access for 
volume: " + volumePath + ", no Sdc connected with host ip: " + 
host.getPrivateIpAddress());
+            }
+
+            final ScaleIOGatewayClient client = 
getScaleIOClient(dataStore.getId());
+            client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(volumePath), 
sdcId);
+        } catch (Exception e) {
+            LOGGER.warn("Failed to revoke access due to: " + e.getMessage(), 
e);
+        }
+    }
+
     private void revokeAccess(DataObject dataObject, EndPoint ep, DataStore 
dataStore) {
         Host host = hostDao.findById(ep.getId());
         revokeAccess(dataObject, host, dataStore);
     }
 
-    private String getConnectedSdc(long poolId, long hostId) {
+    public String getConnectedSdc(long poolId, long hostId) {
         try {
             StoragePoolHostVO poolHostVO = 
storagePoolHostDao.findByPoolHost(poolId, hostId);
             if (poolHostVO == null) {
@@ -443,7 +469,11 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         }
     }
 
-    private CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long 
storagePoolId) {
+    public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long 
storagePoolId) {
+        return createVolume(volumeInfo, storagePoolId, false);
+    }
+
+    public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long 
storagePoolId, boolean migrationInvolved) {
         LOGGER.debug("Creating PowerFlex volume");
 
         StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
@@ -474,7 +504,11 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
             volume.setFolder(scaleIOVolume.getVtreeId());
             volume.setSize(scaleIOVolume.getSizeInKb() * 1024);
             volume.setPoolType(Storage.StoragePoolType.PowerFlex);
-            volume.setFormat(Storage.ImageFormat.RAW);
+            if (volumeInfo.getVolumeType().equals(Volume.Type.ROOT)) {
+                volume.setFormat(volumeInfo.getFormat());
+            } else {
+                volume.setFormat(Storage.ImageFormat.RAW);
+            }
             volume.setPoolId(storagePoolId);
             VolumeObject createdObject = 
VolumeObject.getVolumeObject(volumeInfo.getDataStore(), volume);
             createdObject.update();
@@ -488,7 +522,7 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
             CreateObjectAnswer answer = new 
CreateObjectAnswer(createdObject.getTO());
 
             // if volume needs to be set up with encryption, do it now if it's 
not a root disk (which gets done during template copy)
-            if (anyVolumeRequiresEncryption(volumeInfo) && 
!volumeInfo.getVolumeType().equals(Volume.Type.ROOT)) {
+            if (anyVolumeRequiresEncryption(volumeInfo) && 
(!volumeInfo.getVolumeType().equals(Volume.Type.ROOT) || migrationInvolved)) {
                 LOGGER.debug(String.format("Setting up encryption for volume 
%s", volumeInfo.getId()));
                 VolumeObjectTO prepVolume = (VolumeObjectTO) 
createdObject.getTO();
                 prepVolume.setPath(volumePath);
@@ -682,7 +716,12 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
                     if (isSameScaleIOStorageInstance(srcStore, destStore)) {
                         answer = migrateVolume(srcData, destData);
                     } else {
-                        answer = copyVolume(srcData, destData, destHost);
+                        String vmName = ((VolumeInfo) 
srcData).getAttachedVmName();
+                        if (vmName == null || 
!vmInstanceDao.findVMByInstanceName(vmName).getState().equals(VirtualMachine.State.Running))
 {
+                            answer = copyOfflineVolume(srcData, destData, 
destHost);
+                        } else {
+                            answer = liveMigrateVolume(srcData, destData);
+                        }
                     }
                 } else {
                     errMsg = "Unsupported copy operation from src object: (" + 
srcData.getType() + ", " + srcData.getDataStore() + "), dest object: ("
@@ -702,6 +741,9 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         }
 
         result = new CopyCommandResult(null, answer);
+        if (answer != null && !answer.getResult()) {
+            result.setResult(answer.getDetails());
+        }
         callback.complete(result);
     }
 
@@ -753,7 +795,7 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         return answer;
     }
 
-    private Answer copyVolume(DataObject srcData, DataObject destData, Host 
destHost) {
+    protected Answer copyOfflineVolume(DataObject srcData, DataObject 
destData, Host destHost) {
         // Copy PowerFlex/ScaleIO volume
         LOGGER.debug(String.format("Initiating copy from PowerFlex template 
volume on host %s", destHost != null ? destHost.getId() : "<not specified>"));
         String value = configDao.getValue(Config.CopyVolumeWait.key());
@@ -775,6 +817,227 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         return answer;
     }
 
+    public Answer liveMigrateVolume(DataObject srcData, DataObject destData) {
+        // Volume migration across different PowerFlex/ScaleIO clusters
+        final long srcVolumeId = srcData.getId();
+        DataStore srcStore = srcData.getDataStore();
+        Map<String, String> srcDetails = getVolumeDetails((VolumeInfo) 
srcData, srcStore);
+
+        DataStore destStore = destData.getDataStore();
+        final long destPoolId = destStore.getId();
+        Map<String, String> destDetails = getVolumeDetails((VolumeInfo) 
destData, destStore);
+        VolumeObjectTO destVolTO = (VolumeObjectTO) destData.getTO();
+        String destVolumePath = null;
+
+        Host host = findEndpointForVolumeOperation(srcData);
+        EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(host);
+
+        Answer answer = null;
+        try {
+            CreateObjectAnswer createAnswer = createVolume((VolumeInfo) 
destData, destStore.getId(), true);
+            destVolumePath = createAnswer.getData().getPath();
+            destVolTO.setPath(destVolumePath);
+
+            grantAccess(destData, host, destData.getDataStore());
+
+            int waitInterval = 
NumbersUtil.parseInt(configDao.getValue(Config.MigrateWait.key()), 
Integer.parseInt(Config.MigrateWait.getDefaultValue()));
+            MigrateVolumeCommand migrateVolumeCommand = new 
MigrateVolumeCommand(srcData.getTO(), destVolTO,
+                    srcDetails, destDetails, waitInterval);
+            answer = ep.sendMessage(migrateVolumeCommand);
+            boolean migrateStatus = answer.getResult();
+
+            if (migrateStatus) {
+                updateVolumeAfterCopyVolume(srcData, destData);
+                updateSnapshotsAfterCopyVolume(srcData, destData);
+                deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
+                LOGGER.debug(String.format("Successfully migrated migrate 
PowerFlex volume %d to storage pool %d", srcVolumeId,  destPoolId));
+                answer = new Answer(null, true, null);
+            } else {
+                String errorMsg = "Failed to migrate PowerFlex volume: " + 
srcVolumeId + " to storage pool " + destPoolId;
+                LOGGER.debug(errorMsg);
+                answer = new Answer(null, false, errorMsg);
+            }
+        } catch (Exception e) {
+            LOGGER.error("Failed to migrate PowerFlex volume: " + srcVolumeId 
+ " due to: " + e.getMessage());
+            answer = new Answer(null, false, e.getMessage());
+        }
+
+        if (destVolumePath != null && !answer.getResult()) {
+            revertBlockCopyVolumeOperations(srcData, destData, host, 
destVolumePath);
+        }
+
+        return answer;
+    }
+
+    protected void updateVolumeAfterCopyVolume(DataObject srcData, DataObject 
destData) {
+        // destination volume is already created and volume path is set in 
database by this time at "CreateObjectAnswer createAnswer = 
createVolume((VolumeInfo) destData, destStore.getId());"
+        final long srcVolumeId = srcData.getId();
+        final long destVolumeId = destData.getId();
+
+        if (srcVolumeId != destVolumeId) {
+            VolumeVO srcVolume = volumeDao.findById(srcVolumeId);
+            srcVolume.set_iScsiName(null);
+            srcVolume.setPath(null);
+            srcVolume.setFolder(null);
+            volumeDao.update(srcVolumeId, srcVolume);
+        } else {
+            // Live migrate volume
+            VolumeVO volume = volumeDao.findById(srcVolumeId);
+            Long oldPoolId = volume.getPoolId();
+            volume.setLastPoolId(oldPoolId);
+            volumeDao.update(srcVolumeId, volume);
+        }
+    }
+
+    private Host findEndpointForVolumeOperation(DataObject srcData) {
+        long hostId = 0;
+        VMInstanceVO instance = 
vmInstanceDao.findVMByInstanceName(((VolumeInfo) srcData).getAttachedVmName());
+        if (instance.getState().equals(VirtualMachine.State.Running)) {
+            hostId = instance.getHostId();
+        }
+        if (hostId == 0) {
+            hostId = selector.select(srcData, true).getId();
+        }
+        HostVO host = hostDao.findById(hostId);
+        if (host == null) {
+            throw new CloudRuntimeException("Found no hosts to run migrate 
volume command on");
+        }
+
+        return host;
+    }
+
+    public void updateSnapshotsAfterCopyVolume(DataObject srcData, DataObject 
destData) throws Exception {
+        final long srcVolumeId = srcData.getId();
+        DataStore srcStore = srcData.getDataStore();
+        final long srcPoolId = srcStore.getId();
+        final ScaleIOGatewayClient client = getScaleIOClient(srcPoolId);
+
+        DataStore destStore = destData.getDataStore();
+        final long destPoolId = destStore.getId();
+        final StoragePoolVO destStoragePool = 
storagePoolDao.findById(destPoolId);
+
+        List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcVolumeId);
+        if (CollectionUtils.isNotEmpty(snapshots)) {
+            for (SnapshotVO snapshot : snapshots) {
+                SnapshotDataStoreVO snapshotStore = 
snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+                if (snapshotStore == null) {
+                    continue;
+                }
+
+                String snapshotVolumeId = 
ScaleIOUtil.getVolumePath(snapshotStore.getInstallPath());
+                String newSnapshotName = String.format("%s-%s-%s-%s", 
ScaleIOUtil.SNAPSHOT_PREFIX, snapshot.getId(),
+                        destStoragePool.getUuid().split("-")[0].substring(4), 
ManagementServerImpl.customCsIdentifier.value());
+                boolean renamed = client.renameVolume(snapshotVolumeId, 
newSnapshotName);
+
+                snapshotStore.setDataStoreId(destPoolId);
+                // Snapshot Id in the PowerFlex/ScaleIO pool remains the same 
after the migration
+                // Update PowerFlex snapshot name only after it is renamed, to 
maintain the consistency
+                if (renamed) {
+                    
snapshotStore.setInstallPath(ScaleIOUtil.updatedPathWithVolumeName(snapshotVolumeId,
 newSnapshotName));
+                }
+                snapshotDataStoreDao.update(snapshotStore.getId(), 
snapshotStore);
+            }
+        }
+    }
+
+    public void deleteSourceVolumeAfterSuccessfulBlockCopy(DataObject srcData, 
Host host) {
+        DataStore srcStore = srcData.getDataStore();
+        String srcVolumePath = srcData.getTO().getPath();
+        revokeVolumeAccess(srcVolumePath, host, srcData.getDataStore());
+        String errMsg;
+        try {
+            String scaleIOVolumeId = ScaleIOUtil.getVolumePath(srcVolumePath);
+            final ScaleIOGatewayClient client = 
getScaleIOClient(srcStore.getId());
+            Boolean deleteResult =  client.deleteVolume(scaleIOVolumeId);
+            if (!deleteResult) {
+                errMsg = "Failed to delete source PowerFlex volume with id: " 
+ scaleIOVolumeId;
+                LOGGER.warn(errMsg);
+            }
+        } catch (Exception e) {
+            errMsg = "Unable to delete source PowerFlex volume: " + 
srcVolumePath + " due to " + e.getMessage();
+            LOGGER.warn(errMsg);;
+        }
+    }
+
+    public void revertBlockCopyVolumeOperations(DataObject srcData, DataObject 
destData, Host host, String destVolumePath) {
+        final String srcVolumePath = ((VolumeInfo) srcData).getPath();
+        final String srcVolumeFolder = ((VolumeInfo) srcData).getFolder();
+        DataStore destStore = destData.getDataStore();
+
+        revokeAccess(destData, host, destData.getDataStore());
+        String errMsg;
+        try {
+            String scaleIOVolumeId = ScaleIOUtil.getVolumePath(destVolumePath);
+            final ScaleIOGatewayClient client = 
getScaleIOClient(destStore.getId());
+            Boolean deleteResult =  client.deleteVolume(scaleIOVolumeId);
+            if (!deleteResult) {
+                errMsg = "Failed to delete PowerFlex volume with id: " + 
scaleIOVolumeId;
+                LOGGER.warn(errMsg);
+            }
+
+        } catch (Exception e) {
+            errMsg = "Unable to delete destination PowerFlex volume: " + 
destVolumePath + " due to " + e.getMessage();
+            LOGGER.warn(errMsg);
+            throw new CloudRuntimeException(errMsg, e);
+        }
+
+        final long srcVolumeId = srcData.getId();
+        if (srcVolumeId == destData.getId()) {
+            VolumeVO volume = volumeDao.findById(srcVolumeId);
+            volume.set_iScsiName(srcVolumePath);
+            volume.setPath(srcVolumePath);
+            volume.setFolder(srcVolumeFolder);
+            volume.setPoolId(((VolumeInfo) srcData).getPoolId());
+            volumeDao.update(srcVolumeId, volume);
+        }
+    }
+
+    private Map<String, String> getVolumeDetails(VolumeInfo volumeInfo, 
DataStore dataStore) {
+        long storagePoolId = dataStore.getId();
+        StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePoolId);
+
+        if (!storagePoolVO.isManaged()) {
+            return null;
+        }
+
+        Map<String, String> volumeDetails = new HashMap<>();
+
+        VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId());
+
+        volumeDetails.put(DiskTO.STORAGE_HOST, storagePoolVO.getHostAddress());
+        volumeDetails.put(DiskTO.STORAGE_PORT, 
String.valueOf(storagePoolVO.getPort()));
+        volumeDetails.put(DiskTO.IQN, volumeVO.get_iScsiName());
+        volumeDetails.put(DiskTO.PROTOCOL_TYPE, (volumeVO.getPoolType() != 
null) ? volumeVO.getPoolType().toString() : null);
+        volumeDetails.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), 
String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(storagePoolVO.getId())));
+
+        volumeDetails.put(DiskTO.VOLUME_SIZE, 
String.valueOf(volumeVO.getSize()));
+        volumeDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, 
getVolumeProperty(volumeInfo.getId(), DiskTO.SCSI_NAA_DEVICE_ID));
+
+        ChapInfo chapInfo = volumeService.getChapInfo(volumeInfo, dataStore);
+
+        if (chapInfo != null) {
+            volumeDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, 
chapInfo.getInitiatorUsername());
+            volumeDetails.put(DiskTO.CHAP_INITIATOR_SECRET, 
chapInfo.getInitiatorSecret());
+            volumeDetails.put(DiskTO.CHAP_TARGET_USERNAME, 
chapInfo.getTargetUsername());
+            volumeDetails.put(DiskTO.CHAP_TARGET_SECRET, 
chapInfo.getTargetSecret());
+        }
+
+        String systemId = storagePoolDetailsDao.findDetail(storagePoolId, 
ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
+        volumeDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, 
systemId);
+
+        return volumeDetails;
+    }
+
+    private String getVolumeProperty(long volumeId, String property) {
+        VolumeDetailVO volumeDetails = volumeDetailsDao.findDetail(volumeId, 
property);
+
+        if (volumeDetails != null) {
+            return volumeDetails.getValue();
+        }
+
+        return null;
+    }
+
     private Answer migrateVolume(DataObject srcData, DataObject destData) {
         // Volume migration within same PowerFlex/ScaleIO cluster (with same 
System ID)
         DataStore srcStore = srcData.getDataStore();
@@ -861,7 +1124,7 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         return answer;
     }
 
-    private boolean isSameScaleIOStorageInstance(DataStore srcStore, DataStore 
destStore) {
+    public boolean isSameScaleIOStorageInstance(DataStore srcStore, DataStore 
destStore) {
         long srcPoolId = srcStore.getId();
         String srcPoolSystemId = null;
         StoragePoolDetailVO srcPoolSystemIdDetail = 
storagePoolDetailsDao.findDetail(srcPoolId, 
ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
@@ -1148,7 +1411,7 @@ public class ScaleIOPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
     /**
      * Does any object require encryption support?
      */
-    private boolean anyVolumeRequiresEncryption(DataObject ... objects) {
+    protected boolean anyVolumeRequiresEncryption(DataObject ... objects) {
         for (DataObject o : objects) {
             if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() 
!= null) {
                 return true;
diff --git 
a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java
 
b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java
new file mode 100644
index 00000000000..8480d9751d4
--- /dev/null
+++ 
b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java
@@ -0,0 +1,527 @@
+//
+// 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 org.apache.cloudstack.storage.datastore.driver;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.storage.MigrateVolumeAnswer;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.configuration.Config;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.storage.Storage;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.RemoteHostEndPoint;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Optional;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(RemoteHostEndPoint.class)
+public class ScaleIOPrimaryDataStoreDriverTest {
+
+    @Spy
+    @InjectMocks
+    ScaleIOPrimaryDataStoreDriver scaleIOPrimaryDataStoreDriver = new 
ScaleIOPrimaryDataStoreDriver();
+    @Mock
+    StoragePoolDetailsDao storagePoolDetailsDao;
+    @Mock
+    PrimaryDataStoreDao storagePoolDao;
+    @Mock
+    VolumeDao volumeDao;
+    @Mock
+    VolumeDetailsDao volumeDetailsDao;
+    @Mock
+    VolumeService volumeService;
+    @Mock
+    VMInstanceDao vmInstanceDao;
+    @Mock
+    HostDao hostDao;
+    @Mock
+    ConfigurationDao configDao;
+
+    @Before
+    public void initMocks() {
+        MockitoAnnotations.initMocks(this);
+    }
+    @Test
+    public void testSameScaleIOStorageInstance() {
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        DataStore destStore = Mockito.mock(DataStore.class);
+        when(srcStore.getId()).thenReturn(1L);
+        when(destStore.getId()).thenReturn(2L);
+
+        StoragePoolDetailVO srcPoolSystemIdDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        String srcPoolSystemId = "610204d03e3ad60f";
+        when(srcPoolSystemIdDetail.getValue()).thenReturn(srcPoolSystemId);
+
+        StoragePoolDetailVO destPoolSystemIdDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        String destPoolSystemId = "610204d03e3ad60f";
+        when(destPoolSystemIdDetail.getValue()).thenReturn(destPoolSystemId);
+
+        
when(storagePoolDetailsDao.findDetail(1L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcPoolSystemIdDetail);
+        
when(storagePoolDetailsDao.findDetail(2L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(destPoolSystemIdDetail);
+
+        boolean result = 
scaleIOPrimaryDataStoreDriver.isSameScaleIOStorageInstance(srcStore, destStore);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void testDifferentScaleIOStorageInstance() {
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        DataStore destStore = Mockito.mock(DataStore.class);
+        when(srcStore.getId()).thenReturn(1L);
+        when(destStore.getId()).thenReturn(2L);
+
+        StoragePoolDetailVO srcPoolSystemIdDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        String srcPoolSystemId = "610204d03e3ad60f";
+        when(srcPoolSystemIdDetail.getValue()).thenReturn(srcPoolSystemId);
+
+        StoragePoolDetailVO destPoolSystemIdDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        String destPoolSystemId = "7332760565f6340f";
+        when(destPoolSystemIdDetail.getValue()).thenReturn(destPoolSystemId);
+
+        
when(storagePoolDetailsDao.findDetail(1L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcPoolSystemIdDetail);
+        
when(storagePoolDetailsDao.findDetail(2L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(destPoolSystemIdDetail);
+
+        boolean result = 
scaleIOPrimaryDataStoreDriver.isSameScaleIOStorageInstance(srcStore, destStore);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test (expected = CloudRuntimeException.class)
+    public void 
testCheckVolumeOnDifferentScaleIOStorageInstanceSystemIdShouldNotBeNull() {
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        DataStore destStore = Mockito.mock(DataStore.class);
+        when(srcStore.getId()).thenReturn(1L);
+        when(destStore.getId()).thenReturn(2L);
+
+        StoragePoolDetailVO srcPoolSystemIdDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        String srcPoolSystemId = "610204d03e3ad60f";
+        when(srcPoolSystemIdDetail.getValue()).thenReturn(srcPoolSystemId);
+
+        StoragePoolDetailVO destPoolSystemIdDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        when(destPoolSystemIdDetail.getValue()).thenReturn(null);
+
+        
when(storagePoolDetailsDao.findDetail(1L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcPoolSystemIdDetail);
+        
when(storagePoolDetailsDao.findDetail(2L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(destPoolSystemIdDetail);
+
+        scaleIOPrimaryDataStoreDriver.isSameScaleIOStorageInstance(srcStore, 
destStore);
+    }
+
+    @Test
+    public void testMigrateVolumeWithinSameScaleIOClusterSuccess() throws 
Exception {
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destData = Mockito.mock(VolumeInfo.class);
+
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        DataStore destStore = Mockito.mock(DataStore.class);
+
+        when(srcData.getDataStore()).thenReturn(srcStore);
+        when(destData.getDataStore()).thenReturn(destStore);
+
+        fillSrcVolumeDetails(srcData, srcStore);
+        fillDestVolumeDetails(destData, destStore);
+
+        VolumeObjectTO destVolTO = Mockito.mock(VolumeObjectTO.class);
+        when(destData.getTO()).thenReturn(destVolTO);
+        Host host = prepareEndpointForVolumeOperation(srcData);
+        PowerMockito.mockStatic(RemoteHostEndPoint.class);
+        RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
+        
when(RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
+
+        DataTO dataTO = Mockito.mock(DataTO.class);
+        CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
+        
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData,
 2L, true);
+        when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
+        doReturn(true).when(scaleIOPrimaryDataStoreDriver)
+                .grantAccess(any(), any(), any());
+
+        when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
+        MigrateVolumeAnswer migrateVolumeAnswer = 
Mockito.mock(MigrateVolumeAnswer.class);
+        when(ep.sendMessage(any())).thenReturn(migrateVolumeAnswer);
+        when(migrateVolumeAnswer.getResult()).thenReturn(true);
+
+        Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
+                .updateVolumeAfterCopyVolume(any(), any());
+        Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
+                .updateSnapshotsAfterCopyVolume(any(), any());
+        Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
+                .deleteSourceVolumeAfterSuccessfulBlockCopy(any(), any());
+
+        Answer answer = 
scaleIOPrimaryDataStoreDriver.liveMigrateVolume(srcData, destData);
+
+        Assert.assertTrue(answer.getResult());
+    }
+
+    @Test
+    public void testMigrateVolumeWithinSameScaleIOClusterFailure() throws 
Exception {
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destData = Mockito.mock(VolumeInfo.class);
+
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        DataStore destStore = Mockito.mock(DataStore.class);
+
+        when(srcData.getDataStore()).thenReturn(srcStore);
+        when(destData.getDataStore()).thenReturn(destStore);
+
+        fillSrcVolumeDetails(srcData, srcStore);
+        fillDestVolumeDetails(destData, destStore);
+
+        VolumeObjectTO destVolTO = Mockito.mock(VolumeObjectTO.class);
+        when(destData.getTO()).thenReturn(destVolTO);
+        Host host = prepareEndpointForVolumeOperation(srcData);
+        PowerMockito.mockStatic(RemoteHostEndPoint.class);
+        RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
+        
when(RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
+
+        DataTO dataTO = Mockito.mock(DataTO.class);
+        CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
+        
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData,
 2L, true);
+        when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
+        doReturn(true).when(scaleIOPrimaryDataStoreDriver)
+                .grantAccess(any(), any(), any());
+
+        when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
+        MigrateVolumeAnswer migrateVolumeAnswer = 
Mockito.mock(MigrateVolumeAnswer.class);
+        when(ep.sendMessage(any())).thenReturn(migrateVolumeAnswer);
+        when(migrateVolumeAnswer.getResult()).thenReturn(false);
+        Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
+                .revertBlockCopyVolumeOperations(any(), any(), any(), any());
+
+        Answer answer = 
scaleIOPrimaryDataStoreDriver.liveMigrateVolume(srcData, destData);
+
+        Assert.assertFalse(answer.getResult());
+    }
+
+    private void fillSrcVolumeDetails(VolumeInfo srcData, DataStore srcStore) {
+        when(srcStore.getId()).thenReturn(1L);
+        when(srcData.getId()).thenReturn(1L);
+
+        StoragePoolVO storagePoolVO = Mockito.mock(StoragePoolVO.class);
+        when(storagePoolDao.findById(1L)).thenReturn(storagePoolVO);
+        when(storagePoolVO.isManaged()).thenReturn(true);
+
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        when(volumeDao.findById(1L)).thenReturn(volumeVO);
+
+        when(volumeDetailsDao.findDetail(1L,  
DiskTO.SCSI_NAA_DEVICE_ID)).thenReturn(null);
+        when(volumeService.getChapInfo(srcData, srcStore)).thenReturn(null);
+
+        StoragePoolDetailVO srcStoragePoolDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        when(srcStoragePoolDetail.getValue()).thenReturn("610204d03e3ad60f");
+        when(storagePoolDetailsDao.findDetail(1L, 
ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcStoragePoolDetail);
+    }
+
+    private void fillDestVolumeDetails(VolumeInfo srcData, DataStore srcStore) 
{
+        when(srcStore.getId()).thenReturn(2L);
+        when(srcData.getId()).thenReturn(2L);
+
+        StoragePoolVO storagePoolVO = Mockito.mock(StoragePoolVO.class);
+        when(storagePoolDao.findById(2L)).thenReturn(storagePoolVO);
+        when(storagePoolVO.isManaged()).thenReturn(true);
+
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        when(volumeDao.findById(2L)).thenReturn(volumeVO);
+
+        when(volumeDetailsDao.findDetail(2L,  
DiskTO.SCSI_NAA_DEVICE_ID)).thenReturn(null);
+        when(volumeService.getChapInfo(srcData, srcStore)).thenReturn(null);
+
+        StoragePoolDetailVO srcStoragePoolDetail = 
Mockito.mock(StoragePoolDetailVO.class);
+        when(srcStoragePoolDetail.getValue()).thenReturn("7332760565f6340f");
+        when(storagePoolDetailsDao.findDetail(2L, 
ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcStoragePoolDetail);
+    }
+
+    private Host prepareEndpointForVolumeOperation(VolumeInfo srcData) {
+        VMInstanceVO instance = Mockito.mock(VMInstanceVO.class);
+        when(srcData.getAttachedVmName()).thenReturn("i-2-VM");
+        
when(vmInstanceDao.findVMByInstanceName("i-2-VM")).thenReturn(instance);
+        when(instance.getHostId()).thenReturn(4L);
+        when(instance.getState()).thenReturn(VirtualMachine.State.Running);
+        HostVO host = Mockito.mock(HostVO.class);
+        when(hostDao.findById(4L)).thenReturn(host);
+
+        return host;
+    }
+
+    @Test
+    public void updateVolumeAfterCopyVolumeLiveMigrate() {
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destData = Mockito.mock(VolumeInfo.class);
+
+        when(srcData.getId()).thenReturn(1L);
+        when(destData.getId()).thenReturn(1L);
+
+        VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", 
"root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
+        volume.setPoolId(2L);
+        when(volumeDao.findById(1L)).thenReturn(volume);
+        when(volumeDao.update(1L, volume)).thenReturn(true);
+
+        scaleIOPrimaryDataStoreDriver.updateVolumeAfterCopyVolume(srcData, 
destData);
+
+        Assert.assertEquals(Optional.of(2L), 
Optional.of(volume.getLastPoolId()));
+    }
+
+    @Test
+    public void updateVolumeAfterCopyVolumeOffline() {
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destData = Mockito.mock(VolumeInfo.class);
+
+        when(srcData.getId()).thenReturn(1L);
+        when(destData.getId()).thenReturn(2L);
+
+        VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", 
"root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
+        when(volumeDao.findById(1L)).thenReturn(volume);
+        when(volumeDao.update(1L, volume)).thenReturn(true);
+
+        scaleIOPrimaryDataStoreDriver.updateVolumeAfterCopyVolume(srcData, 
destData);
+
+        Assert.assertNull(volume.get_iScsiName());
+        Assert.assertNull(volume.getPath());
+        Assert.assertNull(volume.getFolder());
+    }
+
+    @Test
+    public void revertBlockCopyVolumeOperationsOnDeleteSuccess() throws 
Exception{
+        //Either destination volume delete success or failure, DB operations 
should get revert
+
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destData = Mockito.mock(VolumeInfo.class);
+        Host host = Mockito.mock(Host.class);
+        String destVolumePath = "01b332b300000007:vol-11-b9e2-10ee";
+
+        when(srcData.getId()).thenReturn(1L);
+        when(srcData.getPoolId()).thenReturn(1L);
+        when(destData.getId()).thenReturn(1L);
+
+        
when(srcData.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
+        when(srcData.getFolder()).thenReturn("921c364500000007");
+        DataStore destStore = Mockito.mock(DataStore.class);
+        when(destStore.getId()).thenReturn(2L);
+        when(destData.getDataStore()).thenReturn(destStore);
+        doNothing().when(scaleIOPrimaryDataStoreDriver)
+                .revokeAccess(any(), any(), any());
+
+        ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
+        doReturn(client).when(scaleIOPrimaryDataStoreDriver)
+                .getScaleIOClient(any());
+        when(client.deleteVolume(any())).thenReturn(true);
+
+        VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", 
"root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
+        when(volumeDao.findById(1L)).thenReturn(volume);
+        when(volumeDao.update(1L, volume)).thenReturn(true);
+
+        scaleIOPrimaryDataStoreDriver.revertBlockCopyVolumeOperations(srcData, 
destData, host, destVolumePath);
+
+        Assert.assertEquals("bec0ba7700000007:vol-11-6aef-10ee", 
volume.get_iScsiName());
+        Assert.assertEquals("bec0ba7700000007:vol-11-6aef-10ee", 
volume.getPath());
+        Assert.assertEquals("921c364500000007", volume.getFolder());
+    }
+
+    @Test
+    public void revertBlockCopyVolumeOperationsOnDeleteFailure() throws 
Exception{
+        //Either destination volume delete success or failure, DB operations 
should get revert
+
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destData = Mockito.mock(VolumeInfo.class);
+        Host host = Mockito.mock(Host.class);
+        String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
+        String destVolumePath = "01b332b300000007:vol-11-b9e2-10ee";
+
+        when(srcData.getId()).thenReturn(1L);
+        when(srcData.getPoolId()).thenReturn(1L);
+        when(destData.getId()).thenReturn(1L);
+
+        when(srcData.getPath()).thenReturn(srcVolumePath);
+        when(srcData.getFolder()).thenReturn("921c364500000007");
+        DataStore destStore = Mockito.mock(DataStore.class);
+        when(destStore.getId()).thenReturn(2L);
+        when(destData.getDataStore()).thenReturn(destStore);
+        doNothing().when(scaleIOPrimaryDataStoreDriver).revokeAccess(any(), 
any(), any());
+
+        ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
+        doReturn(client).when(scaleIOPrimaryDataStoreDriver)
+                .getScaleIOClient(any());
+        when(client.deleteVolume(any())).thenReturn(false);
+
+        VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", 
"root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
+        when(volumeDao.findById(1L)).thenReturn(volume);
+        when(volumeDao.update(1L, volume)).thenReturn(true);
+
+        scaleIOPrimaryDataStoreDriver.revertBlockCopyVolumeOperations(srcData, 
destData, host, destVolumePath);
+
+        Assert.assertEquals(srcVolumePath, volume.get_iScsiName());
+        Assert.assertEquals(srcVolumePath, volume.getPath());
+        Assert.assertEquals("921c364500000007", volume.getFolder());
+    }
+
+    @Test
+    public void deleteSourceVolumeSuccessScenarioAfterSuccessfulBlockCopy() 
throws Exception {
+        // Either Volume deletion success or failure method should complete
+
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        Host host = Mockito.mock(Host.class);
+        String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
+
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        DataTO volumeTO = Mockito.mock(DataTO.class);
+        when(srcData.getDataStore()).thenReturn(srcStore);
+        when(srcData.getTO()).thenReturn(volumeTO);
+        when(volumeTO.getPath()).thenReturn(srcVolumePath);
+        
doNothing().when(scaleIOPrimaryDataStoreDriver).revokeVolumeAccess(any(), 
any(), any());
+
+        ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
+        doReturn(client).when(scaleIOPrimaryDataStoreDriver)
+                .getScaleIOClient(any());
+        when(client.deleteVolume(any())).thenReturn(true);
+
+        
scaleIOPrimaryDataStoreDriver.deleteSourceVolumeAfterSuccessfulBlockCopy(srcData,
 host);
+    }
+
+    @Test
+    public void deleteSourceVolumeFailureScenarioAfterSuccessfulBlockCopy() 
throws Exception {
+        // Either Volume deletion success or failure method should complete
+
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        Host host = Mockito.mock(Host.class);
+        when(host.getId()).thenReturn(1L);
+        String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
+
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        when(srcStore.getId()).thenReturn(1L);
+        DataTO volumeTO = Mockito.mock(DataTO.class);
+        when(srcData.getDataStore()).thenReturn(srcStore);
+        when(srcData.getTO()).thenReturn(volumeTO);
+        when(volumeTO.getPath()).thenReturn(srcVolumePath);
+        String sdcId = "7332760565f6340f";
+        
doReturn(sdcId).when(scaleIOPrimaryDataStoreDriver).getConnectedSdc(1L, 1L);
+
+        ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
+        doReturn(client).when(scaleIOPrimaryDataStoreDriver)
+                .getScaleIOClient(any());
+        doReturn(true).when(client).unmapVolumeFromSdc(any(), any());
+        when(client.deleteVolume(any())).thenReturn(false);
+
+        
scaleIOPrimaryDataStoreDriver.deleteSourceVolumeAfterSuccessfulBlockCopy(srcData,
 host);
+    }
+
+    @Test
+    public void deleteSourceVolumeFailureScenarioWhenNoSDCisFound() {
+        // Either Volume deletion success or failure method should complete
+
+        VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
+        Host host = Mockito.mock(Host.class);
+        when(host.getId()).thenReturn(1L);
+        String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
+
+        DataStore srcStore = Mockito.mock(DataStore.class);
+        when(srcStore.getId()).thenReturn(1L);
+        DataTO volumeTO = Mockito.mock(DataTO.class);
+        when(srcData.getDataStore()).thenReturn(srcStore);
+        when(srcData.getTO()).thenReturn(volumeTO);
+        when(volumeTO.getPath()).thenReturn(srcVolumePath);
+        String sdcId = "7332760565f6340f";
+        doReturn(null).when(scaleIOPrimaryDataStoreDriver).getConnectedSdc(1L, 
1L);
+
+        
scaleIOPrimaryDataStoreDriver.deleteSourceVolumeAfterSuccessfulBlockCopy(srcData,
 host);
+    }
+
+    @Test
+    public void testCopyOfflineVolume() {
+        
when(configDao.getValue(Config.CopyVolumeWait.key())).thenReturn("3600");
+
+        DataObject srcData = Mockito.mock(DataObject.class);
+        DataTO srcDataTO = Mockito.mock(DataTO.class);
+        when(srcData.getTO()).thenReturn(srcDataTO);
+        DataObject destData = Mockito.mock(DataObject.class);
+        DataTO destDataTO = Mockito.mock(DataTO.class);
+        when(destData.getTO()).thenReturn(destDataTO);
+        Host destHost = Mockito.mock(Host.class);
+
+        
doReturn(false).when(scaleIOPrimaryDataStoreDriver).anyVolumeRequiresEncryption(srcData,
 destData);
+        PowerMockito.mockStatic(RemoteHostEndPoint.class);
+        RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
+        
when(RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(ep);
+        Answer answer = Mockito.mock(Answer.class);
+        when(ep.sendMessage(any())).thenReturn(answer);
+
+        Answer expectedAnswer = 
scaleIOPrimaryDataStoreDriver.copyOfflineVolume(srcData, destData, destHost);
+
+        Assert.assertEquals(expectedAnswer, answer);
+    }
+
+    @Test
+    public void testCopyOfflineVolumeFailureWhenNoEndpointFound() {
+        
when(configDao.getValue(Config.CopyVolumeWait.key())).thenReturn("3600");
+
+        DataObject srcData = Mockito.mock(DataObject.class);
+        DataTO srcDataTO = Mockito.mock(DataTO.class);
+        when(srcData.getTO()).thenReturn(srcDataTO);
+        DataObject destData = Mockito.mock(DataObject.class);
+        DataTO destDataTO = Mockito.mock(DataTO.class);
+        when(destData.getTO()).thenReturn(destDataTO);
+        Host destHost = Mockito.mock(Host.class);
+
+        
doReturn(false).when(scaleIOPrimaryDataStoreDriver).anyVolumeRequiresEncryption(srcData,
 destData);
+        PowerMockito.mockStatic(RemoteHostEndPoint.class);
+        
when(RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(null);
+
+        Answer answer = 
scaleIOPrimaryDataStoreDriver.copyOfflineVolume(srcData, destData, destHost);
+
+        Assert.assertEquals(false, answer.getResult());
+    }
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java 
b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index cec78976747..646b81960d6 100644
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@ -92,6 +92,7 @@ import 
org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
 import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
@@ -326,6 +327,8 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
 
     @Inject
     protected ProjectManager projectManager;
+    @Inject
+    protected StoragePoolDetailsDao storagePoolDetailsDao;
 
     protected Gson _gson;
 
@@ -1098,8 +1101,8 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
                 if (isNotPossibleToResize(volume, diskOffering)) {
                     throw new InvalidParameterValueException(
                             "Failed to resize Root volume. The service 
offering of this Volume has been configured with a root disk size; "
-                                    + "on such case a Root Volume can only be 
resized when changing to another Service Offering with a Root disk size. "
-                                    + "For more details please check out the 
Official Resizing Volumes documentation.");
+                                    +   "on such case a Root Volume can only 
be resized when changing to another Service Offering with a Root disk size. "
+                                    +   "For more details please check out the 
Official Resizing Volumes documentation.");
                 }
 
                 // convert from bytes to GiB
@@ -1246,7 +1249,7 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
              */
             if (currentSize > newSize && !shrinkOk) {
                 throw new InvalidParameterValueException("Going from existing 
size of " + currentSize + " to size of " + newSize + " would shrink the volume."
-                        + "Need to sign off by supplying the shrinkok 
parameter with value of true.");
+                        +  "Need to sign off by supplying the shrinkok 
parameter with value of true.");
             }
 
             if (newSize > currentSize) {
@@ -2966,10 +2969,6 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
             vm = _vmInstanceDao.findById(instanceId);
         }
 
-        if (vol.getPassphraseId() != null) {
-            throw new InvalidParameterValueException("Migration of encrypted 
volumes is unsupported");
-        }
-
         // Check that Vm to which this volume is attached does not have VM 
Snapshots
         // OfflineVmwareMigration: consider if this is needed and desirable
         if (vm != null && _vmSnapshotDao.findByVm(vm.getId()).size() > 0) {
@@ -2983,11 +2982,6 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
                 throw new InvalidParameterValueException("Live Migration of 
GPU enabled VM is not supported");
             }
 
-            StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(vol.getPoolId());
-            if (storagePoolVO.getPoolType() == 
Storage.StoragePoolType.PowerFlex) {
-                throw new InvalidParameterValueException("Migrate volume of a 
running VM is unsupported on storage pool type " + storagePoolVO.getPoolType());
-            }
-
             // Check if the underlying hypervisor supports storage motion.
             Long hostId = vm.getHostId();
             if (hostId != null) {
@@ -3002,7 +2996,8 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
                     liveMigrateVolume = 
capabilities.isStorageMotionSupported();
                 }
 
-                if (liveMigrateVolume && 
HypervisorType.KVM.equals(host.getHypervisorType())) {
+                StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(vol.getPoolId());
+                if (liveMigrateVolume && 
HypervisorType.KVM.equals(host.getHypervisorType()) && 
!storagePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
                     StoragePoolVO destinationStoragePoolVo = 
_storagePoolDao.findById(storagePoolId);
 
                     if (isSourceOrDestNotOnStorPool(storagePoolVO, 
destinationStoragePoolVo)) {
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 3f73b620c38..f0aed627f15 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -6270,16 +6270,6 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                     + " hypervisors: [%s].", hypervisorType, 
HYPERVISORS_THAT_CAN_DO_STORAGE_MIGRATION_ON_NON_USER_VMS));
         }
 
-        List<VolumeVO> vols = _volsDao.findByInstance(vm.getId());
-        if (vols.size() > 1) {
-            // OffLineVmwareMigration: data disks are not permitted, here!
-            if (vols.size() > 1 &&
-                    // OffLineVmwareMigration: allow multiple disks for vmware
-                    !HypervisorType.VMware.equals(hypervisorType)) {
-                throw new InvalidParameterValueException("Data disks attached 
to the vm, can not migrate. Need to detach data disks first");
-            }
-        }
-
         // Check that Vm does not have VM Snapshots
         if (_vmSnapshotDao.findByVm(vmId).size() > 0) {
             throw new InvalidParameterValueException("VM's disk cannot be 
migrated, please remove all the VM Snapshots for this VM");
diff --git a/test/integration/plugins/scaleio/test_scaleio_volumes.py 
b/test/integration/plugins/scaleio/test_scaleio_volumes.py
index c67f838297b..d6ba569f90f 100644
--- a/test/integration/plugins/scaleio/test_scaleio_volumes.py
+++ b/test/integration/plugins/scaleio/test_scaleio_volumes.py
@@ -1144,6 +1144,249 @@ class TestScaleIOVolumes(cloudstackTestCase):
 
         test_virtual_machine.delete(self.apiClient, True)
 
+    @attr(tags=['advanced', 'migration'], required_hardware=False)
+    def test_11_live_migrate_volume_to_same_instance_pool(self):
+        '''Migrate volume to the same instance pool'''
+
+        if not TestData.migrationTests:
+            self.skipTest("Volume migration tests not enabled, skipping test")
+
+        #######################################
+        # STEP 1: Create VM and Start VM      #
+        #######################################
+
+        test_virtual_machine = VirtualMachine.create(
+            self.apiClient,
+            self.testdata[TestData.virtualMachine3],
+            accountid=self.account.name,
+            zoneid=self.zone.id,
+            serviceofferingid=self.compute_offering.id,
+            templateid=self.template.id,
+            domainid=self.domain.id,
+            startvm=False
+        )
+
+        TestScaleIOVolumes._start_vm(test_virtual_machine)
+
+        #######################################
+        # STEP 2: Create vol and attach to VM #
+        #######################################
+
+        new_volume = Volume.create(
+            self.apiClient,
+            self.testdata[TestData.volume_3],
+            account=self.account.name,
+            domainid=self.domain.id,
+            zoneid=self.zone.id,
+            diskofferingid=self.disk_offering_same_inst.id
+        )
+
+        volume_to_delete_later = new_volume
+
+        new_volume = test_virtual_machine.attach_volume(
+            self.apiClient,
+            new_volume
+        )
+
+        vm = self._get_vm(test_virtual_machine.id)
+
+        self.assertEqual(
+            new_volume.virtualmachineid,
+            vm.id,
+            "Check if attached to virtual machine"
+        )
+
+        self.assertEqual(
+            vm.state.lower(),
+            'running',
+            str(vm.state)
+        )
+
+        #######################################
+        # STEP 3: Migrate volume  #
+        #######################################
+
+        pools = StoragePool.listForMigration(
+            self.apiClient,
+            id=new_volume.id
+        )
+
+        if not pools:
+            self.skipTest("No suitable storage pools found for volume 
migration, skipping test")
+
+        self.assertEqual(
+            validateList(pools)[0],
+            PASS,
+            "Invalid pool response from findStoragePoolsForMigration API"
+        )
+
+        pool = pools[0]
+        self.debug("Migrating Volume-ID: %s to Same Instance Pool: %s" % 
(new_volume.id, pool.id))
+
+        try:
+            Volume.migrate(
+                self.apiClient,
+                volumeid=new_volume.id,
+                storageid=pool.id
+            )
+        except Exception as e:
+            self.fail("Volume migration failed with error %s" % e)
+
+        #######################################
+        #  STEP 4: Detach and delete volume   #
+        #######################################
+
+        new_volume = test_virtual_machine.detach_volume(
+            self.apiClient,
+            new_volume
+        )
+
+        self.assertEqual(
+            new_volume.virtualmachineid,
+            None,
+            "Check if attached to virtual machine"
+        )
+
+        volume_to_delete_later.delete(self.apiClient)
+
+        list_volumes_response = list_volumes(
+            self.apiClient,
+            id=new_volume.id
+        )
+
+        self.assertEqual(
+            list_volumes_response,
+            None,
+            "Check volume was deleted"
+        )
+
+        #######################################
+        #  STEP 4: Delete VM                  #
+        #######################################
+
+        test_virtual_machine.delete(self.apiClient, True)
+
+    @attr(tags=['advanced', 'migration'], required_hardware=False)
+    def test_12_migrate_volume_to_distinct_instance_pool(self):
+        '''Migrate volume to distinct instance pool'''
+
+        if not TestData.migrationTests:
+            self.skipTest("Volume migration tests not enabled, skipping test")
+
+        #######################################
+        # STEP 1: Create VM and Start VM      #
+        #######################################
+
+        test_virtual_machine = VirtualMachine.create(
+            self.apiClient,
+            self.testdata[TestData.virtualMachine4],
+            accountid=self.account.name,
+            zoneid=self.zone.id,
+            serviceofferingid=self.compute_offering.id,
+            templateid=self.template.id,
+            domainid=self.domain.id,
+            startvm=False
+        )
+
+        TestScaleIOVolumes._start_vm(test_virtual_machine)
+
+        #######################################
+        # STEP 2: Create vol and attach to VM #
+        #######################################
+
+        new_volume = Volume.create(
+            self.apiClient,
+            self.testdata[TestData.volume_4],
+            account=self.account.name,
+            domainid=self.domain.id,
+            zoneid=self.zone.id,
+            diskofferingid=self.disk_offering_distinct_inst.id
+        )
+
+        volume_to_delete_later = new_volume
+
+        new_volume = test_virtual_machine.attach_volume(
+            self.apiClient,
+            new_volume
+        )
+
+        vm = self._get_vm(test_virtual_machine.id)
+
+        self.assertEqual(
+            new_volume.virtualmachineid,
+            vm.id,
+            "Check if attached to virtual machine"
+        )
+
+        self.assertEqual(
+            vm.state.lower(),
+            'running',
+            str(vm.state)
+        )
+
+        #######################################
+        # STEP 3: Migrate volume  #
+        #######################################
+
+        pools = StoragePool.listForMigration(
+            self.apiClient,
+            id=new_volume.id
+        )
+
+        if not pools:
+            self.skipTest("No suitable storage pools found for volume 
migration, skipping test")
+
+        self.assertEqual(
+            validateList(pools)[0],
+            PASS,
+            "Invalid pool response from findStoragePoolsForMigration API"
+        )
+
+        pool = pools[0]
+        self.debug("Migrating Volume-ID: %s to Distinct Instance Pool: %s" % 
(new_volume.id, pool.id))
+
+        try:
+            Volume.migrate(
+                self.apiClient,
+                volumeid=new_volume.id,
+                storageid=pool.id
+            )
+        except Exception as e:
+            self.fail("Volume migration failed with error %s" % e)
+
+        #######################################
+        #  STEP 4: Detach and delete volume   #
+        #######################################
+
+        new_volume = test_virtual_machine.detach_volume(
+            self.apiClient,
+            new_volume
+        )
+
+        self.assertEqual(
+            new_volume.virtualmachineid,
+            None,
+            "Check if attached to virtual machine"
+        )
+
+        volume_to_delete_later.delete(self.apiClient)
+
+        list_volumes_response = list_volumes(
+            self.apiClient,
+            id=new_volume.id
+        )
+
+        self.assertEqual(
+            list_volumes_response,
+            None,
+            "Check volume was deleted"
+        )
+
+        #######################################
+        #  STEP 4: Delete VM                  #
+        #######################################
+
+        test_virtual_machine.delete(self.apiClient, True)
 
     def _create_vm_using_template_and_destroy_vm(self, template):
         vm_name = "VM-%d" % random.randint(0, 100)

Reply via email to