cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ro...@apache.org
Subject [cloudstack] branch master updated: kvm: live storage migration intra cluster from NFS source and destination (#2983)
Date Mon, 10 Jun 2019 10:05:36 GMT
This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/master by this push:
     new 0fbf500  kvm: live storage migration intra cluster from NFS source and destination (#2983)
0fbf500 is described below

commit 0fbf5006b87b6c5bc6188e3e1dd226a98c3bb453
Author: Nicolas Vazquez <nicovazquez90@gmail.com>
AuthorDate: Mon Jun 10 07:05:26 2019 -0300

    kvm: live storage migration intra cluster from NFS source and destination (#2983)
    
    Feature Specification: https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=95653548
    
    Live storage migration on KVM under these conditions:
    
    From source and destination hosts within the same cluster
    From NFS primary storage to NFS cluster-wide primary storage
    Source NFS and destination NFS storage mounted on hosts
    In order to enable this functionality, database should be updated in order to enable live storage capacibilty for KVM, if previous conditions are met. This is due to existing conflicts between qemu and libvirt versions. This has been tested on CentOS 6 hosts.
    
    Additional notes:
    
    To use this feature set the storage_motion_supported=1 in the hypervisor_capability table for KVM. This is done by default as the feature may not work in some environments, read below.
    This feature of online storage+VM migration for KVM will only work with CentOS6 and possible Ubuntu as KVM hosts but not with CentOS7 due to:
    https://bugs.centos.org/view.php?id=14026
    https://bugzilla.redhat.com/show_bug.cgi?id=1219541
    On CentOS7 the error we see is: " error: unable to execute QEMU command 'migrate': this feature or command is not currently supported" (reference https://ask.openstack.org/en/question/94186/live-migration-unable-to-execute-qemu-command-migrate/). Reading through various lists looks like the migrate feature with qemu may be available with paid versions of RHEL-EV but not centos7 however this works with CentOS6.
    Fix for CentOS 7:
    
    Create repo file on /etc/yum.repos.d/:
    [qemu-kvm-rhev]
    name=oVirt rebuilds of qemu-kvm-rhev
    baseurl=http://resources.ovirt.org/pub/ovirt-3.5/rpm/el7Server/
    mirrorlist=http://resources.ovirt.org/pub/yum-repo/mirrorlist-ovirt-3.5-el7Server
    enabled=1
    skip_if_unavailable=1
    gpgcheck=0
    yum install qemu-kvm-common-ev-2.3.0-29.1.el7.x86_64 qemu-kvm-ev-2.3.0-29.1.el7.x86_64 qemu-img-ev-2.3.0-29.1.el7.x86_64
    Reboot host
    
    Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
---
 .../java/com/cloud/storage/MigrationOptions.java   |  86 ++++++
 .../cloud/agent/api/CreateVMSnapshotCommand.java   |   4 +
 .../cloud/agent/api/DeleteVMSnapshotCommand.java   |   4 +
 .../java/com/cloud/agent/api/MigrateCommand.java   |   9 +
 .../CheckStorageAvailabilityCommand.java}          |  25 +-
 .../cloudstack/storage/to/VolumeObjectTO.java      |   7 +
 .../engine/subsystem/api/storage/VolumeInfo.java   |   8 +
 .../java/com/cloud/storage/StorageManager.java     |  10 +-
 .../com/cloud/vm/VirtualMachineManagerImpl.java    |  11 +-
 .../KvmNonManagedStorageDataMotionStrategy.java    |  67 ++++-
 .../motion/StorageSystemDataMotionStrategy.java    | 303 ++++++++++++++++-----
 .../KvmNonManagedStorageSystemDataMotionTest.java  | 150 +++++++++-
 .../StorageSystemDataMotionStrategyTest.java       |  37 +--
 .../cloudstack/storage/volume/VolumeObject.java    |  12 +
 .../hypervisor/kvm/resource/MigrateKVMAsync.java   |  82 ++++--
 .../LibvirtCheckStorageAvailabilityWrapper.java    |  61 +++++
 .../wrapper/LibvirtMigrateCommandWrapper.java      |  13 +-
 ...tupDirectDownloadCertificateCommandWrapper.java |   0
 .../kvm/storage/IscsiAdmStorageAdaptor.java        |   7 +-
 .../kvm/storage/KVMStoragePoolManager.java         |  10 +-
 .../kvm/storage/KVMStorageProcessor.java           |  66 ++++-
 .../kvm/storage/LibvirtStorageAdaptor.java         |  62 ++++-
 .../kvm/storage/ManagedNfsStorageAdaptor.java      |   7 +-
 .../hypervisor/kvm/storage/StorageAdaptor.java     |  10 +-
 .../org/apache/cloudstack/utils/qemu/QemuImg.java  |  49 +++-
 .../wrapper/LibvirtMigrateCommandWrapperTest.java  | 141 +++++++++-
 .../main/java/com/cloud/configuration/Config.java  |   8 -
 .../java/com/cloud/storage/StorageManagerImpl.java |  13 +-
 .../com/cloud/template/TemplateManagerImpl.java    |   6 +-
 test/integration/smoke/test_snapshots.py           |   6 +-
 test/integration/smoke/test_vm_life_cycle.py       | 212 +++++++++++++-
 31 files changed, 1281 insertions(+), 205 deletions(-)

diff --git a/api/src/main/java/com/cloud/storage/MigrationOptions.java b/api/src/main/java/com/cloud/storage/MigrationOptions.java
new file mode 100644
index 0000000..38c1ee8
--- /dev/null
+++ b/api/src/main/java/com/cloud/storage/MigrationOptions.java
@@ -0,0 +1,86 @@
+/*
+ * 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.storage;
+
+import java.io.Serializable;
+
+public class MigrationOptions implements Serializable {
+
+    private String srcPoolUuid;
+    private Storage.StoragePoolType srcPoolType;
+    private Type type;
+    private String srcBackingFilePath;
+    private boolean copySrcTemplate;
+    private String srcVolumeUuid;
+    private int timeout;
+
+    public enum Type {
+        LinkedClone, FullClone
+    }
+
+    public MigrationOptions() {
+    }
+
+    public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcBackingFilePath, boolean copySrcTemplate) {
+        this.srcPoolUuid = srcPoolUuid;
+        this.srcPoolType = srcPoolType;
+        this.type = Type.LinkedClone;
+        this.srcBackingFilePath = srcBackingFilePath;
+        this.copySrcTemplate = copySrcTemplate;
+    }
+
+    public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcVolumeUuid) {
+        this.srcPoolUuid = srcPoolUuid;
+        this.srcPoolType = srcPoolType;
+        this.type = Type.FullClone;
+        this.srcVolumeUuid = srcVolumeUuid;
+    }
+
+    public String getSrcPoolUuid() {
+        return srcPoolUuid;
+    }
+
+    public Storage.StoragePoolType getSrcPoolType() {
+        return srcPoolType;
+    }
+
+    public String getSrcBackingFilePath() {
+        return srcBackingFilePath;
+    }
+
+    public boolean isCopySrcTemplate() {
+        return copySrcTemplate;
+    }
+
+    public String getSrcVolumeUuid() {
+        return srcVolumeUuid;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public int getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(int timeout) {
+        this.timeout = timeout;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java
index 1455145..945f462 100644
--- a/core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java
@@ -32,6 +32,10 @@ public class CreateVMSnapshotCommand extends VMSnapshotBaseCommand {
         this.vmUuid = vmUuid;
     }
 
+    public CreateVMSnapshotCommand(String vmName, VMSnapshotTO snapshot) {
+        super(vmName, snapshot, null, null);
+    }
+
     public String getVmUuid() {
         return vmUuid;
     }
diff --git a/core/src/main/java/com/cloud/agent/api/DeleteVMSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/DeleteVMSnapshotCommand.java
index cd18697..e6e5b81 100644
--- a/core/src/main/java/com/cloud/agent/api/DeleteVMSnapshotCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/DeleteVMSnapshotCommand.java
@@ -27,4 +27,8 @@ public class DeleteVMSnapshotCommand extends VMSnapshotBaseCommand {
     public DeleteVMSnapshotCommand(String vmName, VMSnapshotTO snapshot, List<VolumeObjectTO> volumeTOs, String guestOSType) {
         super(vmName, snapshot, volumeTOs, guestOSType);
     }
+
+    public DeleteVMSnapshotCommand(String vmName, VMSnapshotTO snapshot) {
+        super(vmName, snapshot, null, null);
+    }
 }
diff --git a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java
index e31287c..f189c72 100644
--- a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java
@@ -30,6 +30,7 @@ public class MigrateCommand extends Command {
     private String vmName;
     private String destIp;
     private Map<String, MigrateDiskInfo> migrateStorage;
+    private boolean migrateStorageManaged;
     private boolean autoConvergence;
     private String hostGuid;
     private boolean isWindows;
@@ -56,6 +57,14 @@ public class MigrateCommand extends Command {
         return migrateStorage != null ? new HashMap<>(migrateStorage) : new HashMap<String, MigrateDiskInfo>();
     }
 
+    public boolean isMigrateStorageManaged() {
+        return migrateStorageManaged;
+    }
+
+    public void setMigrateStorageManaged(boolean migrateStorageManaged) {
+        this.migrateStorageManaged = migrateStorageManaged;
+    }
+
     public void setAutoConvergence(boolean autoConvergence) {
         this.autoConvergence = autoConvergence;
     }
diff --git a/core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/storage/CheckStorageAvailabilityCommand.java
similarity index 60%
copy from core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java
copy to core/src/main/java/com/cloud/agent/api/storage/CheckStorageAvailabilityCommand.java
index 1455145..f063599 100644
--- a/core/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/storage/CheckStorageAvailabilityCommand.java
@@ -17,22 +17,27 @@
 // under the License.
 //
 
-package com.cloud.agent.api;
+package com.cloud.agent.api.storage;
 
-import java.util.List;
+import com.cloud.agent.api.Command;
+import com.cloud.storage.Storage;
 
-import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import java.util.Map;
 
+public class CheckStorageAvailabilityCommand extends Command {
 
-public class CreateVMSnapshotCommand extends VMSnapshotBaseCommand {
-    private String vmUuid;
+    private Map<String, Storage.StoragePoolType> poolsMap;
 
-    public CreateVMSnapshotCommand(String vmName, String vmUuid, VMSnapshotTO snapshot, List<VolumeObjectTO> volumeTOs, String guestOSType) {
-        super(vmName, snapshot, volumeTOs, guestOSType);
-        this.vmUuid = vmUuid;
+    public CheckStorageAvailabilityCommand(Map<String, Storage.StoragePoolType> poolsMap) {
+        this.poolsMap = poolsMap;
     }
 
-    public String getVmUuid() {
-        return vmUuid;
+    public Map<String, Storage.StoragePoolType> getPoolsMap() {
+        return poolsMap;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
     }
 }
diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
index 2d240c4..5a9ff21 100644
--- a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
+++ b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
@@ -19,6 +19,7 @@
 
 package org.apache.cloudstack.storage.to;
 
+import com.cloud.storage.MigrationOptions;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 
 import com.cloud.agent.api.to.DataObjectType;
@@ -59,6 +60,7 @@ public class VolumeObjectTO implements DataTO {
     private Long iopsWriteRateMaxLength;
     private DiskCacheMode cacheMode;
     private Hypervisor.HypervisorType hypervisorType;
+    private MigrationOptions migrationOptions;
 
     public VolumeObjectTO() {
 
@@ -97,6 +99,7 @@ public class VolumeObjectTO implements DataTO {
         cacheMode = volume.getCacheMode();
         hypervisorType = volume.getHypervisorType();
         setDeviceId(volume.getDeviceId());
+        this.migrationOptions = volume.getMigrationOptions();
     }
 
     public String getUuid() {
@@ -300,4 +303,8 @@ public class VolumeObjectTO implements DataTO {
     public DiskCacheMode getCacheMode() {
         return cacheMode;
     }
+
+    public MigrationOptions getMigrationOptions() {
+        return migrationOptions;
+    }
 }
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
index 6794272..99e47df 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
@@ -21,6 +21,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage;
 import com.cloud.agent.api.Answer;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.offering.DiskOffering.DiskCacheMode;
+import com.cloud.storage.MigrationOptions;
 import com.cloud.storage.Volume;
 import com.cloud.vm.VirtualMachine;
 
@@ -71,4 +72,11 @@ public interface VolumeInfo extends DataObject, Volume {
     Long getIopsWriteRateMaxLength();
 
     DiskCacheMode getCacheMode();
+
+    /**
+     * Currently available for KVM volumes
+     */
+    MigrationOptions getMigrationOptions();
+
+    void setMigrationOptions(MigrationOptions migrationOptions);
 }
diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java
index fd98101..62a241b 100644
--- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java
+++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java
@@ -87,11 +87,19 @@ public interface StorageManager extends StorageService {
     ConfigKey<Integer> KvmStorageOnlineMigrationWait = new ConfigKey<>(Integer.class,
             "kvm.storage.online.migration.wait",
             "Storage",
-            "10800",
+            "86400",
             "Timeout in seconds for online (live) storage migration to complete on KVM (migrateVirtualMachineWithVolume)",
             true,
             ConfigKey.Scope.Global,
             null);
+    ConfigKey<Boolean> KvmAutoConvergence = new ConfigKey<>(Boolean.class,
+            "kvm.auto.convergence",
+            "Storage",
+            "false",
+            "Setting this to 'true' allows KVM to use auto convergence to complete VM migration (libvirt version 1.2.3+ and QEMU version 1.6+)",
+            true,
+            ConfigKey.Scope.Global,
+            null);
     ConfigKey<Integer> MaxNumberOfManagedClusteredFileSystems = new ConfigKey<>(Integer.class,
             "max.number.managed.clustered.file.systems",
             "Storage",
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 7d218e2..354323e 100755
--- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -120,7 +120,6 @@ import com.cloud.agent.manager.Commands;
 import com.cloud.agent.manager.allocator.HostAllocator;
 import com.cloud.alert.AlertManager;
 import com.cloud.capacity.CapacityManager;
-import com.cloud.configuration.Config;
 import com.cloud.dc.ClusterDetailsDao;
 import com.cloud.dc.ClusterDetailsVO;
 import com.cloud.dc.DataCenter;
@@ -2367,11 +2366,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
             final boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
             final MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows, to, getExecuteInSequence(vm.getHypervisorType()));
 
-            String autoConvergence = _configDao.getValue(Config.KvmAutoConvergence.toString());
-            boolean kvmAutoConvergence = Boolean.parseBoolean(autoConvergence);
-
+            boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value();
             mc.setAutoConvergence(kvmAutoConvergence);
-
             mc.setHostGuid(dest.getHost().getGuid());
 
             try {
@@ -3897,11 +3893,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
             final boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
             final MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows, to, getExecuteInSequence(vm.getHypervisorType()));
 
-            String autoConvergence = _configDao.getValue(Config.KvmAutoConvergence.toString());
-            boolean kvmAutoConvergence = Boolean.parseBoolean(autoConvergence);
-
+            boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value();
             mc.setAutoConvergence(kvmAutoConvergence);
-
             mc.setHostGuid(dest.getHost().getGuid());
 
             try {
diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
index 1bbe177..e42715a 100644
--- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
+++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java
@@ -24,6 +24,8 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import com.cloud.storage.ScopeType;
+import com.cloud.storage.Storage;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@@ -54,6 +56,7 @@ import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VirtualMachineManager;
+import org.apache.commons.collections.MapUtils;
 
 /**
  * Extends {@link StorageSystemDataMotionStrategy}, allowing KVM hosts to migrate VMs with the ROOT volume on a non managed local storage pool.
@@ -77,14 +80,16 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
      * Note that the super implementation (override) is called by {@link #canHandle(Map, Host, Host)} which ensures that {@link #internalCanHandle(Map)} will be executed only if the source host is KVM.
      */
     @Override
-    protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap) {
-        if (super.internalCanHandle(volumeMap) == StrategyPriority.CANT_HANDLE) {
-            Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
+    protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
+        if (super.internalCanHandle(volumeMap, srcHost, destHost) == StrategyPriority.CANT_HANDLE) {
+            if (canHandleKVMNonManagedLiveNFSStorageMigration(volumeMap, srcHost, destHost) == StrategyPriority.CANT_HANDLE) {
+                Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
 
-            for (VolumeInfo volumeInfo : volumeInfoSet) {
-                StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
-                if (storagePoolVO.getPoolType() != StoragePoolType.Filesystem && storagePoolVO.getPoolType() != StoragePoolType.NetworkFilesystem) {
-                    return StrategyPriority.CANT_HANDLE;
+                for (VolumeInfo volumeInfo : volumeInfoSet) {
+                    StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
+                    if (storagePoolVO.getPoolType() != StoragePoolType.Filesystem && storagePoolVO.getPoolType() != StoragePoolType.NetworkFilesystem) {
+                        return StrategyPriority.CANT_HANDLE;
+                    }
                 }
             }
             return StrategyPriority.HYPERVISOR;
@@ -93,6 +98,52 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
     }
 
     /**
+     * Allow KVM live storage migration for non managed storage when:
+     * - Source host and destination host are different, and are on the same cluster
+     * - Source and destination storage are NFS
+     * - Destination storage is cluster-wide
+     */
+    protected StrategyPriority canHandleKVMNonManagedLiveNFSStorageMigration(Map<VolumeInfo, DataStore> volumeMap,
+                                                                             Host srcHost, Host destHost) {
+        if (srcHost.getId() != destHost.getId() &&
+                srcHost.getClusterId().equals(destHost.getClusterId()) &&
+                isSourceNfsPrimaryStorage(volumeMap) &&
+                isDestinationNfsPrimaryStorageClusterWide(volumeMap)) {
+            return StrategyPriority.HYPERVISOR;
+        }
+        return StrategyPriority.CANT_HANDLE;
+    }
+
+    /**
+     * True if volumes source storage are NFS
+     */
+    protected boolean isSourceNfsPrimaryStorage(Map<VolumeInfo, DataStore> volumeMap) {
+        if (MapUtils.isNotEmpty(volumeMap)) {
+            for (VolumeInfo volumeInfo : volumeMap.keySet()) {
+                StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
+                return storagePoolVO != null &&
+                        storagePoolVO.getPoolType() == Storage.StoragePoolType.NetworkFilesystem;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if destination storage is cluster-wide NFS
+     */
+    protected boolean isDestinationNfsPrimaryStorageClusterWide(Map<VolumeInfo, DataStore> volumeMap) {
+        if (MapUtils.isNotEmpty(volumeMap)) {
+            for (DataStore dataStore : volumeMap.values()) {
+                StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
+                return storagePoolVO != null &&
+                        storagePoolVO.getPoolType() == Storage.StoragePoolType.NetworkFilesystem &&
+                        storagePoolVO.getScope() == ScopeType.CLUSTER;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume and calls rootImageProvisioning.
      */
     @Override
@@ -135,7 +186,7 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
      */
     @Override
     protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, Host destHost, StoragePoolVO destStoragePool) {
-        return sourceStoragePool.getPoolType() == StoragePoolType.Filesystem;
+        return sourceStoragePool.getPoolType() == StoragePoolType.Filesystem || sourceStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem;
     }
 
     /**
diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
index 1f3368f..45cd295 100644
--- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
+++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
@@ -18,16 +18,71 @@
  */
 package org.apache.cloudstack.storage.motion;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
+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.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.StorageAction;
+import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
+import org.apache.cloudstack.framework.async.AsyncCallFuture;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.command.CopyCmdAnswer;
+import org.apache.cloudstack.storage.command.CopyCommand;
+import org.apache.cloudstack.storage.command.ResignatureAnswer;
+import org.apache.cloudstack.storage.command.ResignatureCommand;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
-import com.cloud.agent.api.storage.CopyVolumeAnswer;
-import com.cloud.agent.api.storage.CopyVolumeCommand;
 import com.cloud.agent.api.MigrateAnswer;
 import com.cloud.agent.api.MigrateCommand;
 import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
 import com.cloud.agent.api.ModifyTargetsAnswer;
 import com.cloud.agent.api.ModifyTargetsCommand;
 import com.cloud.agent.api.PrepareForMigrationCommand;
+import com.cloud.agent.api.storage.CheckStorageAvailabilityCommand;
+import com.cloud.agent.api.storage.CopyVolumeAnswer;
+import com.cloud.agent.api.storage.CopyVolumeCommand;
 import com.cloud.agent.api.storage.MigrateVolumeAnswer;
 import com.cloud.agent.api.storage.MigrateVolumeCommand;
 import com.cloud.agent.api.to.DataStoreTO;
@@ -46,15 +101,20 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.resource.ResourceState;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.MigrationOptions;
+import com.cloud.storage.ScopeType;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.GuestOSCategoryDao;
@@ -63,73 +123,18 @@ import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.SnapshotDetailsDao;
 import com.cloud.storage.dao.SnapshotDetailsVO;
 import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.dao.VolumeDetailsDao;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.db.GlobalLock;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineManager;
-import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.dao.VMInstanceDao;
-
 import com.google.common.base.Preconditions;
 
-import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
-import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
-import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
-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.DataStoreCapabilities;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
-import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
-import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
-import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
-import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
-import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
-import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
-import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
-import org.apache.cloudstack.engine.subsystem.api.storage.StorageAction;
-import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
-import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
-import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
-import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
-import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
-import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
-import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult;
-import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
-import org.apache.cloudstack.framework.async.AsyncCallFuture;
-import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.storage.command.CopyCmdAnswer;
-import org.apache.cloudstack.storage.command.CopyCommand;
-import org.apache.cloudstack.storage.command.ResignatureAnswer;
-import org.apache.cloudstack.storage.command.ResignatureCommand;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
-import org.apache.cloudstack.storage.to.VolumeObjectTO;
-import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
-
-import org.springframework.stereotype.Component;
-
-import javax.inject.Inject;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-@Component
 public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
     private static final Logger LOGGER = Logger.getLogger(StorageSystemDataMotionStrategy.class);
     private static final Random RANDOM = new Random(System.nanoTime());
@@ -176,6 +181,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
     private StorageCacheManager cacheMgr;
     @Inject
     private EndPointSelector selector;
+    @Inject
+    VMTemplatePoolDao templatePoolDao;
 
     @Override
     public StrategyPriority canHandle(DataObject srcData, DataObject destData) {
@@ -272,7 +279,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
     @Override
     public final StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
         if (HypervisorType.KVM.equals(srcHost.getHypervisorType())) {
-            return internalCanHandle(volumeMap);
+            return internalCanHandle(volumeMap, srcHost, destHost);
         }
         return StrategyPriority.CANT_HANDLE;
     }
@@ -280,7 +287,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
     /**
      * Handles migrating volumes on managed Storage.
      */
-    protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap) {
+    protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
         Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
 
         for (VolumeInfo volumeInfo : volumeInfoSet) {
@@ -299,6 +306,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             if (storagePoolVO.isManaged()) {
                 return StrategyPriority.HIGHEST;
             }
+
         }
         return StrategyPriority.CANT_HANDLE;
     }
@@ -1699,6 +1707,50 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
     }
 
     /**
+     * Return expected MigrationOptions for a linked clone volume live storage migration
+     */
+    protected MigrationOptions createLinkedCloneMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, String srcVolumeBackingFile, String srcPoolUuid, Storage.StoragePoolType srcPoolType) {
+        VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(destVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId());
+        boolean updateBackingFileReference = ref == null;
+        String backingFile = ref != null ? ref.getInstallPath() : srcVolumeBackingFile;
+        return new MigrationOptions(srcPoolUuid, srcPoolType, backingFile, updateBackingFileReference);
+    }
+
+    /**
+     * Return expected MigrationOptions for a full clone volume live storage migration
+     */
+    protected MigrationOptions createFullCloneMigrationOptions(VolumeInfo srcVolumeInfo, VirtualMachineTO vmTO, Host srcHost, String srcPoolUuid, Storage.StoragePoolType srcPoolType) {
+        return new MigrationOptions(srcPoolUuid, srcPoolType, srcVolumeInfo.getPath());
+    }
+
+    /**
+     * Prepare hosts for KVM live storage migration depending on volume type by setting MigrationOptions on destination volume:
+     * - Linked clones (backing file on disk): Decide if template (backing file) should be copied to destination storage prior disk creation
+     * - Full clones (no backing file): Take snapshot of the VM prior disk creation
+     * Return this information
+     */
+    protected void setVolumeMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
+                                             VirtualMachineTO vmTO, Host srcHost, StoragePoolVO destStoragePool) {
+        if (!destStoragePool.isManaged()) {
+            String srcVolumeBackingFile = getVolumeBackingFile(srcVolumeInfo);
+
+            String srcPoolUuid = srcVolumeInfo.getDataStore().getUuid();
+            StoragePoolVO srcPool = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
+            Storage.StoragePoolType srcPoolType = srcPool.getPoolType();
+
+            MigrationOptions migrationOptions;
+            if (StringUtils.isNotBlank(srcVolumeBackingFile)) {
+                migrationOptions = createLinkedCloneMigrationOptions(srcVolumeInfo, destVolumeInfo,
+                        srcVolumeBackingFile, srcPoolUuid, srcPoolType);
+            } else {
+                migrationOptions = createFullCloneMigrationOptions(srcVolumeInfo, vmTO, srcHost, srcPoolUuid, srcPoolType);
+            }
+            migrationOptions.setTimeout(StorageManager.KvmStorageOnlineMigrationWait.value());
+            destVolumeInfo.setMigrationOptions(migrationOptions);
+        }
+    }
+
+    /**
      * For each disk to migrate:
      * <ul>
      *  <li>Create a volume on the target storage system.</li>
@@ -1716,7 +1768,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
                 throw new CloudRuntimeException("Invalid hypervisor type (only KVM supported for this operation at the time being)");
             }
 
-            verifyLiveMigrationMapForKVM(volumeDataStoreMap);
+            verifyLiveMigrationForKVM(volumeDataStoreMap, destHost);
 
             VMInstanceVO vmInstance = _vmDao.findById(vmTO.getId());
             vmTO.setState(vmInstance.getState());
@@ -1725,6 +1777,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
             Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
 
+            boolean managedStorageDestination = false;
             for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
                 VolumeInfo srcVolumeInfo = entry.getKey();
                 DataStore destDataStore = entry.getValue();
@@ -1749,15 +1802,23 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
                 // move the volume from Ready to Migrating
                 destVolumeInfo.processEvent(Event.MigrationRequested);
 
+                setVolumeMigrationOptions(srcVolumeInfo, destVolumeInfo, vmTO, srcHost, destStoragePool);
+
                 // create a volume on the destination storage
                 destDataStore.getDriver().createAsync(destDataStore, destVolumeInfo, null);
 
+                managedStorageDestination = destStoragePool.isManaged();
+                String volumeIdentifier = managedStorageDestination ? destVolumeInfo.get_iScsiName() : destVolumeInfo.getUuid();
+
                 destVolume = _volumeDao.findById(destVolume.getId());
+                destVolume.setPath(volumeIdentifier);
 
                 setVolumePath(destVolume);
 
                 _volumeDao.update(destVolume.getId(), destVolume);
 
+                postVolumeCreationActions(srcVolumeInfo, destVolumeInfo, vmTO, srcHost);
+
                 destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
 
                 handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
@@ -1766,9 +1827,18 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 
                 String destPath = generateDestPath(destHost, destStoragePool, destVolumeInfo);
 
-                MigrateCommand.MigrateDiskInfo migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath);
-                migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
-                migrateDiskInfoList.add(migrateDiskInfo);
+                MigrateCommand.MigrateDiskInfo migrateDiskInfo;
+                if (managedStorageDestination) {
+                    migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath);
+                    migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
+                    migrateDiskInfoList.add(migrateDiskInfo);
+                } else {
+                    migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
+                            MigrateCommand.MigrateDiskInfo.DiskType.FILE,
+                            MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
+                            MigrateCommand.MigrateDiskInfo.Source.FILE,
+                            connectHostToVolume(destHost, destVolumeInfo.getPoolId(), volumeIdentifier));
+                }
 
                 migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
 
@@ -1795,15 +1865,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
 
             MigrateCommand migrateCommand = new MigrateCommand(vmTO.getName(), destHost.getPrivateIpAddress(), isWindows, vmTO, true);
-
             migrateCommand.setWait(StorageManager.KvmStorageOnlineMigrationWait.value());
-
             migrateCommand.setMigrateStorage(migrateStorage);
             migrateCommand.setMigrateDiskInfoList(migrateDiskInfoList);
+            migrateCommand.setMigrateStorageManaged(managedStorageDestination);
 
-            String autoConvergence = _configDao.getValue(Config.KvmAutoConvergence.toString());
-            boolean kvmAutoConvergence = Boolean.parseBoolean(autoConvergence);
-
+            boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value();
             migrateCommand.setAutoConvergence(kvmAutoConvergence);
 
             MigrateAnswer migrateAnswer = (MigrateAnswer)agentManager.send(srcHost.getId(), migrateCommand);
@@ -1863,7 +1930,9 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
      * Configures a {@link MigrateDiskInfo} object with disk type of BLOCK, Driver type RAW and Source DEV
      */
     protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath) {
-        return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, MigrateCommand.MigrateDiskInfo.DriverType.RAW,
+        return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
+                MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
+                MigrateCommand.MigrateDiskInfo.DriverType.RAW,
                 MigrateCommand.MigrateDiskInfo.Source.DEV, destPath);
     }
 
@@ -1883,6 +1952,21 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         // This method is used by classes that extend this one
     }
 
+    /*
+     * Return backing file for volume (if any), only for KVM volumes
+     */
+    private String getVolumeBackingFile(VolumeInfo srcVolumeInfo) {
+        if (srcVolumeInfo.getHypervisorType() == HypervisorType.KVM &&
+                srcVolumeInfo.getTemplateId() != null && srcVolumeInfo.getPoolId() != null) {
+            VMTemplateVO template = _vmTemplateDao.findById(srcVolumeInfo.getTemplateId());
+            if (template.getFormat() != null && template.getFormat() != Storage.ImageFormat.ISO) {
+                VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(srcVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId());
+                return ref != null ? ref.getInstallPath() : null;
+            }
+        }
+        return null;
+    }
+
     private void handlePostMigration(boolean success, Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host destHost) {
         if (!success) {
             try {
@@ -2046,10 +2130,40 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         return modifyTargetsAnswer.getConnectedPaths();
     }
 
+    /**
+     * Update reference on template_spool_ref table of copied template to destination storage
+     */
+    protected void updateCopiedTemplateReference(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) {
+        VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(srcVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId());
+        VMTemplateStoragePoolVO newRef = new VMTemplateStoragePoolVO(destVolumeInfo.getPoolId(), ref.getTemplateId());
+        newRef.setDownloadPercent(100);
+        newRef.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
+        newRef.setState(ObjectInDataStoreStateMachine.State.Ready);
+        newRef.setTemplateSize(ref.getTemplateSize());
+        newRef.setLocalDownloadPath(ref.getLocalDownloadPath());
+        newRef.setInstallPath(ref.getInstallPath());
+        templatePoolDao.persist(newRef);
+    }
+
+    /**
+     * Handle post destination volume creation actions depending on the migrating volume type: full clone or linked clone
+     */
+    protected void postVolumeCreationActions(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, VirtualMachineTO vmTO, Host srcHost) {
+        MigrationOptions migrationOptions = destVolumeInfo.getMigrationOptions();
+        if (migrationOptions != null) {
+            if (migrationOptions.getType() == MigrationOptions.Type.LinkedClone && migrationOptions.isCopySrcTemplate()) {
+                updateCopiedTemplateReference(srcVolumeInfo, destVolumeInfo);
+            }
+        }
+    }
+
     /*
-    * At a high level: The source storage cannot be managed and the destination storage must be managed.
+    * At a high level: The source storage cannot be managed and
+    *                  the destination storages can be all managed or all not managed, not mixed.
     */
-    private void verifyLiveMigrationMapForKVM(Map<VolumeInfo, DataStore> volumeDataStoreMap) {
+    protected void verifyLiveMigrationForKVM(Map<VolumeInfo, DataStore> volumeDataStoreMap, Host destHost) {
+        Boolean storageTypeConsistency = null;
+        Map<String, Storage.StoragePoolType> sourcePools = new HashMap<>();
         for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
             VolumeInfo volumeInfo = entry.getKey();
 
@@ -2070,6 +2184,47 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             if (destStoragePoolVO == null) {
                 throw new CloudRuntimeException("Destination storage pool with ID " + dataStore.getId() + " was not located.");
             }
+
+            if (storageTypeConsistency == null) {
+                storageTypeConsistency = destStoragePoolVO.isManaged();
+            } else if (storageTypeConsistency != destStoragePoolVO.isManaged()) {
+                throw new CloudRuntimeException("Destination storage pools must be either all managed or all not managed");
+            }
+
+            if (!destStoragePoolVO.isManaged()) {
+                if (destStoragePoolVO.getPoolType() == StoragePoolType.NetworkFilesystem &&
+                        destStoragePoolVO.getScope() != ScopeType.CLUSTER) {
+                    throw new CloudRuntimeException("KVM live storage migrations currently support cluster-wide " +
+                            "not managed NFS destination storage");
+                }
+                if (!sourcePools.containsKey(srcStoragePoolVO.getUuid())) {
+                    sourcePools.put(srcStoragePoolVO.getUuid(), srcStoragePoolVO.getPoolType());
+                }
+            }
+        }
+        verifyDestinationStorage(sourcePools, destHost);
+    }
+
+    /**
+     * Perform storage validation on destination host for KVM live storage migrations.
+     * Validate that volume source storage pools are mounted on the destination host prior the migration
+     * @throws CloudRuntimeException if any source storage pool is not mounted on the destination host
+     */
+    private void verifyDestinationStorage(Map<String, Storage.StoragePoolType> sourcePools, Host destHost) {
+        if (MapUtils.isNotEmpty(sourcePools)) {
+            LOGGER.debug("Verifying source pools are already available on destination host " + destHost.getUuid());
+            CheckStorageAvailabilityCommand cmd = new CheckStorageAvailabilityCommand(sourcePools);
+            try {
+                Answer answer = agentManager.send(destHost.getId(), cmd);
+                if (answer == null || !answer.getResult()) {
+                    throw new CloudRuntimeException("Storage verification failed on host "
+                            + destHost.getUuid() +": " + answer.getDetails());
+                }
+            } catch (AgentUnavailableException | OperationTimedoutException e) {
+                e.printStackTrace();
+                throw new CloudRuntimeException("Cannot perform storage verification on host " + destHost.getUuid() +
+                        "due to: " + e.getMessage());
+            }
         }
     }
 
diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java
index 89a2783..5b8d3af 100644
--- a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java
+++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java
@@ -21,6 +21,10 @@ package org.apache.cloudstack.storage.motion;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.cloud.host.Host;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.ScopeType;
+import com.cloud.storage.Storage;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@@ -38,6 +42,7 @@ import org.apache.cloudstack.storage.image.store.ImageStoreImpl;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.volume.VolumeObject;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
@@ -53,7 +58,6 @@ import com.cloud.agent.api.MigrateCommand;
 import com.cloud.exception.AgentUnavailableException;
 import com.cloud.exception.CloudException;
 import com.cloud.exception.OperationTimedoutException;
-import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.storage.DataStoreRole;
@@ -66,6 +70,9 @@ import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VirtualMachineManager;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
 @RunWith(MockitoJUnitRunner.class)
 public class KvmNonManagedStorageSystemDataMotionTest {
 
@@ -90,6 +97,36 @@ public class KvmNonManagedStorageSystemDataMotionTest {
     @InjectMocks
     private KvmNonManagedStorageDataMotionStrategy kvmNonManagedStorageDataMotionStrategy;
 
+    @Mock
+    VolumeInfo volumeInfo1;
+    @Mock
+    VolumeInfo volumeInfo2;
+    @Mock
+    DataStore dataStore1;
+    @Mock
+    DataStore dataStore2;
+    @Mock
+    DataStore dataStore3;
+    @Mock
+    StoragePoolVO pool1;
+    @Mock
+    StoragePoolVO pool2;
+    @Mock
+    StoragePoolVO pool3;
+    @Mock
+    Host host1;
+    @Mock
+    Host host2;
+
+    Map<VolumeInfo, DataStore> migrationMap;
+
+    private static final Long POOL_1_ID = 1L;
+    private static final Long POOL_2_ID = 2L;
+    private static final Long POOL_3_ID = 3L;
+    private static final Long HOST_1_ID = 1L;
+    private static final Long HOST_2_ID = 2L;
+    private static final Long CLUSTER_ID = 1L;
+
     @Test
     public void canHandleTestExpectHypervisorStrategyForKvm() {
         canHandleExpectCannotHandle(HypervisorType.KVM, 1, StrategyPriority.HYPERVISOR);
@@ -109,12 +146,13 @@ public class KvmNonManagedStorageSystemDataMotionTest {
 
     private void canHandleExpectCannotHandle(HypervisorType hypervisorType, int times, StrategyPriority expectedStrategyPriority) {
         HostVO srcHost = new HostVO("sourceHostUuid");
+        HostVO destHost = new HostVO("destHostUuid");
         srcHost.setHypervisorType(hypervisorType);
-        Mockito.doReturn(StrategyPriority.HYPERVISOR).when(kvmNonManagedStorageDataMotionStrategy).internalCanHandle(new HashMap<>());
+        Mockito.doReturn(StrategyPriority.HYPERVISOR).when(kvmNonManagedStorageDataMotionStrategy).internalCanHandle(new HashMap<>(), srcHost, destHost);
 
-        StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.canHandle(new HashMap<>(), srcHost, new HostVO("destHostUuid"));
+        StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.canHandle(new HashMap<>(), srcHost, destHost);
 
-        Mockito.verify(kvmNonManagedStorageDataMotionStrategy, Mockito.times(times)).internalCanHandle(new HashMap<>());
+        Mockito.verify(kvmNonManagedStorageDataMotionStrategy, Mockito.times(times)).internalCanHandle(new HashMap<>(), srcHost, destHost);
         Assert.assertEquals(expectedStrategyPriority, strategyPriority);
     }
 
@@ -123,7 +161,7 @@ public class KvmNonManagedStorageSystemDataMotionTest {
         StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
         for (int i = 0; i < storagePoolTypeArray.length; i++) {
             Map<VolumeInfo, DataStore> volumeMap = configureTestInternalCanHandle(false, storagePoolTypeArray[i]);
-            StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.internalCanHandle(volumeMap);
+            StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.internalCanHandle(volumeMap, new HostVO("sourceHostUuid"), new HostVO("destHostUuid"));
             if (storagePoolTypeArray[i] == StoragePoolType.Filesystem || storagePoolTypeArray[i] == StoragePoolType.NetworkFilesystem) {
                 Assert.assertEquals(StrategyPriority.HYPERVISOR, strategyPriority);
             } else {
@@ -137,7 +175,7 @@ public class KvmNonManagedStorageSystemDataMotionTest {
         StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
         for (int i = 0; i < storagePoolTypeArray.length; i++) {
             Map<VolumeInfo, DataStore> volumeMap = configureTestInternalCanHandle(true, storagePoolTypeArray[i]);
-            StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.internalCanHandle(volumeMap);
+            StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.internalCanHandle(volumeMap, null, null);
             Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
         }
     }
@@ -202,7 +240,7 @@ public class KvmNonManagedStorageSystemDataMotionTest {
         for (int i = 0; i < storagePoolTypes.length; i++) {
             Mockito.doReturn(storagePoolTypes[i]).when(sourceStoragePool).getPoolType();
             boolean result = kvmNonManagedStorageDataMotionStrategy.shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool);
-            if (storagePoolTypes[i] == StoragePoolType.Filesystem) {
+            if (storagePoolTypes[i] == StoragePoolType.Filesystem || storagePoolTypes[i] == StoragePoolType.NetworkFilesystem) {
                 Assert.assertTrue(result);
             } else {
                 Assert.assertFalse(result);
@@ -330,4 +368,102 @@ public class KvmNonManagedStorageSystemDataMotionTest {
         verifyInOrder.verify(kvmNonManagedStorageDataMotionStrategy, Mockito.times(times)).sendCopyCommand(Mockito.eq(destHost), Mockito.any(TemplateObjectTO.class),
                 Mockito.any(TemplateObjectTO.class), Mockito.eq(destDataStore));
     }
+
+    @Before
+    public void setUp() {
+        migrationMap = new HashMap<>();
+        migrationMap.put(volumeInfo1, dataStore2);
+        migrationMap.put(volumeInfo2, dataStore2);
+
+        when(volumeInfo1.getPoolId()).thenReturn(POOL_1_ID);
+        when(primaryDataStoreDao.findById(POOL_1_ID)).thenReturn(pool1);
+        when(pool1.isManaged()).thenReturn(false);
+        when(dataStore2.getId()).thenReturn(POOL_2_ID);
+        when(primaryDataStoreDao.findById(POOL_2_ID)).thenReturn(pool2);
+        when(pool2.isManaged()).thenReturn(true);
+        when(volumeInfo1.getDataStore()).thenReturn(dataStore1);
+
+        when(volumeInfo2.getPoolId()).thenReturn(POOL_1_ID);
+        when(volumeInfo2.getDataStore()).thenReturn(dataStore1);
+
+        when(dataStore1.getId()).thenReturn(POOL_1_ID);
+        when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        when(pool2.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        when(pool2.getScope()).thenReturn(ScopeType.CLUSTER);
+
+        when(dataStore3.getId()).thenReturn(POOL_3_ID);
+        when(primaryDataStoreDao.findById(POOL_3_ID)).thenReturn(pool3);
+        when(pool3.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        when(pool3.getScope()).thenReturn(ScopeType.CLUSTER);
+        when(host1.getId()).thenReturn(HOST_1_ID);
+        when(host1.getClusterId()).thenReturn(CLUSTER_ID);
+        when(host1.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
+        when(host2.getId()).thenReturn(HOST_2_ID);
+        when(host2.getClusterId()).thenReturn(CLUSTER_ID);
+        when(host2.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
+    }
+
+    @Test
+    public void canHandleKVMLiveStorageMigrationSameHost() {
+        StrategyPriority priority = kvmNonManagedStorageDataMotionStrategy.canHandleKVMNonManagedLiveNFSStorageMigration(migrationMap, host1, host1);
+        assertEquals(StrategyPriority.CANT_HANDLE, priority);
+    }
+
+    @Test
+    public void canHandleKVMLiveStorageMigrationInterCluster() {
+        when(host2.getClusterId()).thenReturn(5L);
+        StrategyPriority priority = kvmNonManagedStorageDataMotionStrategy.canHandleKVMNonManagedLiveNFSStorageMigration(migrationMap, host1, host2);
+        assertEquals(StrategyPriority.CANT_HANDLE, priority);
+    }
+
+    @Test
+    public void canHandleKVMLiveStorageMigration() {
+        StrategyPriority priority = kvmNonManagedStorageDataMotionStrategy.canHandleKVMNonManagedLiveNFSStorageMigration(migrationMap, host1, host2);
+        assertEquals(StrategyPriority.HYPERVISOR, priority);
+    }
+
+    @Test
+    public void canHandleKVMLiveStorageMigrationMultipleSources() {
+        when(volumeInfo1.getDataStore()).thenReturn(dataStore2);
+        StrategyPriority priority = kvmNonManagedStorageDataMotionStrategy.canHandleKVMNonManagedLiveNFSStorageMigration(migrationMap, host1, host2);
+        assertEquals(StrategyPriority.HYPERVISOR, priority);
+    }
+
+    @Test
+    public void canHandleKVMLiveStorageMigrationMultipleDestination() {
+        migrationMap.put(volumeInfo2, dataStore3);
+        StrategyPriority priority = kvmNonManagedStorageDataMotionStrategy.canHandleKVMNonManagedLiveNFSStorageMigration(migrationMap, host1, host2);
+        assertEquals(StrategyPriority.HYPERVISOR, priority);
+    }
+
+    @Test
+    public void testCanHandleLiveMigrationUnmanagedStorage() {
+        when(pool2.isManaged()).thenReturn(false);
+        StrategyPriority priority = kvmNonManagedStorageDataMotionStrategy.canHandleKVMNonManagedLiveNFSStorageMigration(migrationMap, host1, host2);
+        assertEquals(StrategyPriority.HYPERVISOR, priority);
+    }
+
+    @Test
+    public void testVerifyLiveMigrationMapForKVM() {
+        kvmNonManagedStorageDataMotionStrategy.verifyLiveMigrationForKVM(migrationMap, host2);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testVerifyLiveMigrationMapForKVMNotExistingSource() {
+        when(primaryDataStoreDao.findById(POOL_1_ID)).thenReturn(null);
+        kvmNonManagedStorageDataMotionStrategy.verifyLiveMigrationForKVM(migrationMap, host2);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testVerifyLiveMigrationMapForKVMNotExistingDest() {
+        when(primaryDataStoreDao.findById(POOL_2_ID)).thenReturn(null);
+        kvmNonManagedStorageDataMotionStrategy.verifyLiveMigrationForKVM(migrationMap, host2);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testVerifyLiveMigrationMapForKVMMixedManagedUnmagedStorage() {
+        when(pool1.isManaged()).thenReturn(true);
+        when(pool2.isManaged()).thenReturn(false);
+        kvmNonManagedStorageDataMotionStrategy.verifyLiveMigrationForKVM(migrationMap, host2);
+    }
 }
diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
index 197e66c..1b383d9 100644
--- a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
+++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
@@ -51,8 +51,8 @@ import com.cloud.host.HostVO;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.ImageStore;
 import com.cloud.storage.Storage;
-import com.cloud.storage.Volume;
 import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -60,14 +60,14 @@ public class StorageSystemDataMotionStrategyTest {
 
     @Spy
     @InjectMocks
-    private StorageSystemDataMotionStrategy storageSystemDataMotionStrategy;
+    private StorageSystemDataMotionStrategy strategy;
 
     @Mock
     private VolumeObject volumeObjectSource;
     @Mock
     private DataObject dataObjectDestination;
     @Mock
-    private PrimaryDataStore primaryDataStoreSourceStore;
+    private PrimaryDataStore sourceStore;
     @Mock
     private ImageStore destinationStore;
     @Mock
@@ -75,26 +75,26 @@ public class StorageSystemDataMotionStrategyTest {
 
     @Before
     public void setUp() throws Exception {
-        primaryDataStoreSourceStore = mock(PrimaryDataStoreImpl.class);
+        sourceStore = mock(PrimaryDataStoreImpl.class);
         destinationStore = mock(ImageStoreImpl.class);
         volumeObjectSource = mock(VolumeObject.class);
         dataObjectDestination = mock(VolumeObject.class);
 
-        initMocks(storageSystemDataMotionStrategy);
+        initMocks(strategy);
     }
 
     @Test
     public void cantHandleSecondary() {
-        doReturn(primaryDataStoreSourceStore).when(volumeObjectSource).getDataStore();
-        doReturn(DataStoreRole.Primary).when(primaryDataStoreSourceStore).getRole();
+        doReturn(sourceStore).when(volumeObjectSource).getDataStore();
+        doReturn(DataStoreRole.Primary).when(sourceStore).getRole();
         doReturn(destinationStore).when(dataObjectDestination).getDataStore();
         doReturn(DataStoreRole.Image).when((DataStore)destinationStore).getRole();
-        doReturn(primaryDataStoreSourceStore).when(volumeObjectSource).getDataStore();
+        doReturn(sourceStore).when(volumeObjectSource).getDataStore();
         doReturn(destinationStore).when(dataObjectDestination).getDataStore();
         StoragePoolVO storeVO = new StoragePoolVO();
         doReturn(storeVO).when(primaryDataStoreDao).findById(0l);
 
-        assertTrue(storageSystemDataMotionStrategy.canHandle(volumeObjectSource, dataObjectDestination) == StrategyPriority.CANT_HANDLE);
+        assertTrue(strategy.canHandle(volumeObjectSource, dataObjectDestination) == StrategyPriority.CANT_HANDLE);
     }
 
     @Test
@@ -135,7 +135,7 @@ public class StorageSystemDataMotionStrategyTest {
         Mockito.doReturn(storagePool0).when(primaryDataStoreDao).findById(0l);
         Mockito.doReturn(storagePool1).when(primaryDataStoreDao).findById(1l);
 
-        StrategyPriority strategyPriority = storageSystemDataMotionStrategy.internalCanHandle(volumeMap);
+        StrategyPriority strategyPriority = strategy.internalCanHandle(volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
 
         Assert.assertEquals(expectedStrategyPriority, strategyPriority);
     }
@@ -146,7 +146,7 @@ public class StorageSystemDataMotionStrategyTest {
         StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
         for (int i = 0; i < storagePoolTypeArray.length; i++) {
             Mockito.doReturn(storagePoolTypeArray[i]).when(sourceStoragePool).getPoolType();
-            boolean result = storageSystemDataMotionStrategy.isStoragePoolTypeOfFile(sourceStoragePool);
+            boolean result = strategy.isStoragePoolTypeOfFile(sourceStoragePool);
             if (sourceStoragePool.getPoolType() == StoragePoolType.Filesystem) {
                 Assert.assertTrue(result);
             } else {
@@ -161,19 +161,19 @@ public class StorageSystemDataMotionStrategyTest {
         HostVO destHost = new HostVO("guid");
         Mockito.doReturn("iScsiName").when(destVolumeInfo).get_iScsiName();
         Mockito.doReturn(0l).when(destVolumeInfo).getPoolId();
-        Mockito.doReturn("expected").when(storageSystemDataMotionStrategy).connectHostToVolume(destHost, 0l, "iScsiName");
+        Mockito.doReturn("expected").when(strategy).connectHostToVolume(destHost, 0l, "iScsiName");
 
-        String expected = storageSystemDataMotionStrategy.generateDestPath(destHost, Mockito.mock(StoragePoolVO.class), destVolumeInfo);
+        String expected = strategy.generateDestPath(destHost, Mockito.mock(StoragePoolVO.class), destVolumeInfo);
 
         Assert.assertEquals(expected, "expected");
-        Mockito.verify(storageSystemDataMotionStrategy).connectHostToVolume(destHost, 0l, "iScsiName");
+        Mockito.verify(strategy).connectHostToVolume(destHost, 0l, "iScsiName");
     }
 
     @Test
     public void configureMigrateDiskInfoTest() {
         VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
         Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
-        MigrateCommand.MigrateDiskInfo migrateDiskInfo = storageSystemDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
+        MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
         Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
         Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
         Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
@@ -187,7 +187,7 @@ public class StorageSystemDataMotionStrategyTest {
         String volumePath = "iScsiName";
         volume.set_iScsiName(volumePath);
 
-        storageSystemDataMotionStrategy.setVolumePath(volume);
+        strategy.setVolumePath(volume);
 
         Assert.assertEquals(volumePath, volume.getPath());
     }
@@ -200,8 +200,9 @@ public class StorageSystemDataMotionStrategyTest {
         StoragePoolType[] storagePoolTypes = StoragePoolType.values();
         for (int i = 0; i < storagePoolTypes.length; i++) {
             Mockito.doReturn(storagePoolTypes[i]).when(sourceStoragePool).getPoolType();
-            boolean result = storageSystemDataMotionStrategy.shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool);
+            boolean result = strategy.shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool);
             Assert.assertTrue(result);
         }
     }
-}
+
+}
\ No newline at end of file
diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
index 85d9524..d62a0ba 100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
@@ -20,6 +20,7 @@ import java.util.Date;
 
 import javax.inject.Inject;
 
+import com.cloud.storage.MigrationOptions;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
@@ -72,6 +73,7 @@ public class VolumeObject implements VolumeInfo {
     @Inject
     DiskOfferingDao diskOfferingDao;
     private Object payload;
+    private MigrationOptions migrationOptions;
 
     public VolumeObject() {
         _volStateMachine = Volume.State.getStateMachine();
@@ -315,6 +317,16 @@ public class VolumeObject implements VolumeInfo {
         return null;
     }
 
+    @Override
+    public MigrationOptions getMigrationOptions() {
+        return migrationOptions;
+    }
+
+    @Override
+    public void setMigrationOptions(MigrationOptions migrationOptions) {
+        this.migrationOptions = migrationOptions;
+    }
+
     public void update() {
         volumeDao.update(volumeVO.getId(), volumeVO);
         volumeVO = volumeDao.findById(volumeVO.getId());
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java
index 51dbd92..c3e3e6e 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java
@@ -34,46 +34,67 @@ public class MigrateKVMAsync implements Callable<Domain> {
     private String vmName = "";
     private String destIp = "";
     private boolean migrateStorage;
+    private boolean migrateStorageManaged;
     private boolean autoConvergence;
 
-    /**
-     * Do not pause the domain during migration. The domain's memory will be transferred to the destination host while the domain is running. The migration may never converge if the domain is changing its memory faster then it can be transferred. The domain can be manually paused anytime during migration using virDomainSuspend.
-     * @value 1
-     * @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
-     */
+    // Libvirt Migrate Flags reference:
+    // https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags
+
+    // Do not pause the domain during migration. The domain's memory will be
+    // transferred to the destination host while the domain is running. The migration
+    // may never converge if the domain is changing its memory faster then it can be
+    // transferred. The domain can be manually paused anytime during migration using
+    // virDomainSuspend.
     private static final long VIR_MIGRATE_LIVE = 1L;
-    /**
-     * Migrate full disk images in addition to domain's memory. By default only non-shared non-readonly disk images are transferred. The VIR_MIGRATE_PARAM_MIGRATE_DISKS parameter can be used to specify which disks should be migrated. This flag and VIR_MIGRATE_NON_SHARED_INC are mutually exclusive.
-     * @value 64
-     * @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
-     */
+
+    // Define the domain as persistent on the destination host after successful
+    // migration. If the domain was persistent on the source host and
+    // VIR_MIGRATE_UNDEFINE_SOURCE is not used, it will end up persistent on both
+    // hosts.
+    private static final long VIR_MIGRATE_PERSIST_DEST = 8L;
+
+    // Migrate full disk images in addition to domain's memory. By default only
+    // non-shared non-readonly disk images are transferred. The
+    // VIR_MIGRATE_PARAM_MIGRATE_DISKS parameter can be used to specify which disks
+    // should be migrated. This flag and VIR_MIGRATE_NON_SHARED_INC are mutually
+    // exclusive.
     private static final long VIR_MIGRATE_NON_SHARED_DISK = 64L;
-    /**
-     * Compress migration data. The compression methods can be specified using VIR_MIGRATE_PARAM_COMPRESSION. A hypervisor default method will be used if this parameter is omitted. Individual compression methods can be tuned via their specific VIR_MIGRATE_PARAM_COMPRESSION_* parameters.
-     * @value 2048
-     * @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
-     */
+
+    // Migrate disk images in addition to domain's memory. This is similar to
+    // VIR_MIGRATE_NON_SHARED_DISK, but only the top level of each disk's backing chain
+    // is copied. That is, the rest of the backing chain is expected to be present on
+    // the destination and to be exactly the same as on the source host. This flag and
+    // VIR_MIGRATE_NON_SHARED_DISK are mutually exclusive.
+    private static final long VIR_MIGRATE_NON_SHARED_INC = 128L;
+
+    // Compress migration data. The compression methods can be specified using
+    // VIR_MIGRATE_PARAM_COMPRESSION. A hypervisor default method will be used if this
+    // parameter is omitted. Individual compression methods can be tuned via their
+    // specific VIR_MIGRATE_PARAM_COMPRESSION_* parameters.
     private static final long VIR_MIGRATE_COMPRESSED = 2048L;
-    /**
-     * Enable algorithms that ensure a live migration will eventually converge. This usually means the domain will be slowed down to make sure it does not change its memory faster than a hypervisor can transfer the changed memory to the destination host. VIR_MIGRATE_PARAM_AUTO_CONVERGE_* parameters can be used to tune the algorithm.
-     * @value 8192
-     * @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
-     */
+
+    // Enable algorithms that ensure a live migration will eventually converge.
+    // This usually means the domain will be slowed down to make sure it does not
+    // change its memory faster than a hypervisor can transfer the changed memory to
+    // the destination host. VIR_MIGRATE_PARAM_AUTO_CONVERGE_* parameters can be used
+    // to tune the algorithm.
     private static final long VIR_MIGRATE_AUTO_CONVERGE = 8192L;
 
-    /**
-     *  Libvirt 1.0.3 supports compression flag for migration.
-     */
+    // Libvirt 1.0.3 supports compression flag for migration.
     private static final int LIBVIRT_VERSION_SUPPORTS_MIGRATE_COMPRESSED = 1000003;
 
+    // Libvirt 1.2.3 supports auto converge.
+    private static final int LIBVIRT_VERSION_SUPPORTS_AUTO_CONVERGE = 1002003;
+
     public MigrateKVMAsync(final LibvirtComputingResource libvirtComputingResource, final Domain dm, final Connect dconn, final String dxml,
-                           final boolean migrateStorage, final boolean autoConvergence, final String vmName, final String destIp) {
+                           final boolean migrateStorage, final boolean migrateStorageManaged, final boolean autoConvergence, final String vmName, final String destIp) {
         this.libvirtComputingResource = libvirtComputingResource;
 
         this.dm = dm;
         this.dconn = dconn;
         this.dxml = dxml;
         this.migrateStorage = migrateStorage;
+        this.migrateStorageManaged = migrateStorageManaged;
         this.autoConvergence = autoConvergence;
         this.vmName = vmName;
         this.destIp = destIp;
@@ -84,15 +105,20 @@ public class MigrateKVMAsync implements Callable<Domain> {
         long flags = VIR_MIGRATE_LIVE;
 
         if (dconn.getLibVirVersion() >= LIBVIRT_VERSION_SUPPORTS_MIGRATE_COMPRESSED) {
-            flags += VIR_MIGRATE_COMPRESSED;
+            flags |= VIR_MIGRATE_COMPRESSED;
         }
 
         if (migrateStorage) {
-            flags += VIR_MIGRATE_NON_SHARED_DISK;
+            if (migrateStorageManaged) {
+                flags |= VIR_MIGRATE_NON_SHARED_DISK;
+            } else {
+                flags |= VIR_MIGRATE_PERSIST_DEST;
+                flags |= VIR_MIGRATE_NON_SHARED_INC;
+            }
         }
 
-        if (autoConvergence && dconn.getLibVirVersion() >= 1002003) {
-            flags += VIR_MIGRATE_AUTO_CONVERGE;
+        if (autoConvergence && dconn.getLibVirVersion() >= LIBVIRT_VERSION_SUPPORTS_AUTO_CONVERGE) {
+            flags |= VIR_MIGRATE_AUTO_CONVERGE;
         }
 
         return dm.migrate(dconn, flags, dxml, vmName, "tcp:" + destIp, libvirtComputingResource.getMigrateSpeed());
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckStorageAvailabilityWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckStorageAvailabilityWrapper.java
new file mode 100644
index 0000000..5022e01
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckStorageAvailabilityWrapper.java
@@ -0,0 +1,61 @@
+//
+// 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.CheckStorageAvailabilityCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+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 com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+
+@ResourceWrapper(handles =  CheckStorageAvailabilityCommand.class)
+public class LibvirtCheckStorageAvailabilityWrapper extends CommandWrapper<CheckStorageAvailabilityCommand, Answer, LibvirtComputingResource> {
+
+    private static final Logger s_logger = Logger.getLogger(LibvirtCheckStorageAvailabilityWrapper.class);
+
+    @Override
+    public Answer execute(CheckStorageAvailabilityCommand command, LibvirtComputingResource resource) {
+        KVMStoragePoolManager storagePoolMgr = resource.getStoragePoolMgr();
+        Map<String, Storage.StoragePoolType> poolsMap = command.getPoolsMap();
+
+        for (String poolUuid : poolsMap.keySet()) {
+            Storage.StoragePoolType type = poolsMap.get(poolUuid);
+            s_logger.debug("Checking if storage pool " + poolUuid + " (" + type + ") is mounted on this host");
+            try {
+                KVMStoragePool storagePool = storagePoolMgr.getStoragePool(type, poolUuid);
+                if (storagePool == null) {
+                    s_logger.info("Storage pool " + poolUuid + " is not available");
+                    return new Answer(command, false, "Storage pool " + poolUuid + " not available");
+                }
+            } catch (CloudRuntimeException e) {
+                s_logger.info("Storage pool " + poolUuid + " is not available");
+                return new Answer(command, e);
+            }
+        }
+        return new Answer(command);
+    }
+}
\ No newline at end of file
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
index 0c1370e..5bf8d25 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
@@ -147,9 +147,10 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
             // migrateStorage is declared as final because the replaceStorage method may mutate mapMigrateStorage, but
             // migrateStorage's value should always only be associated with the initial state of mapMigrateStorage.
             final boolean migrateStorage = MapUtils.isNotEmpty(mapMigrateStorage);
+            final boolean migrateStorageManaged = command.isMigrateStorageManaged();
 
             if (migrateStorage) {
-                xmlDesc = replaceStorage(xmlDesc, mapMigrateStorage);
+                xmlDesc = replaceStorage(xmlDesc, mapMigrateStorage, migrateStorageManaged);
             }
 
             dconn = libvirtUtilitiesHelper.retrieveQemuConnection(destinationUri);
@@ -157,7 +158,8 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
             //run migration in thread so we can monitor it
             s_logger.info("Live migration of instance " + vmName + " initiated to destination host: " + dconn.getURI());
             final ExecutorService executor = Executors.newFixedThreadPool(1);
-            final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc, migrateStorage,
+            final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc,
+                    migrateStorage, migrateStorageManaged,
                     command.isAutoConvergence(), vmName, command.getDestinationIp());
             final Future<Domain> migrateThread = executor.submit(worker);
             executor.shutdown();
@@ -356,7 +358,8 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
      *  <li>The source of the disk needs an attribute that is either 'file' or 'dev' as well as its corresponding value.
      * </ul>
      */
-    protected String replaceStorage(String xmlDesc, Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage)
+    protected String replaceStorage(String xmlDesc, Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage,
+                                  boolean migrateStorageManaged)
             throws IOException, ParserConfigurationException, SAXException, TransformerException {
         InputStream in = IOUtils.toInputStream(xmlDesc);
 
@@ -398,7 +401,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
                             for (int z = 0; z < diskChildNodes.getLength(); z++) {
                                 Node diskChildNode = diskChildNodes.item(z);
 
-                                if ("driver".equals(diskChildNode.getNodeName())) {
+                                if (migrateStorageManaged && "driver".equals(diskChildNode.getNodeName())) {
                                     Node driverNode = diskChildNode;
 
                                     NamedNodeMap driverNodeAttributes = driverNode.getAttributes();
@@ -413,7 +416,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
                                     newChildSourceNode.setAttribute(migrateDiskInfo.getSource().toString(), migrateDiskInfo.getSourceText());
 
                                     diskNode.appendChild(newChildSourceNode);
-                                } else if ("auth".equals(diskChildNode.getNodeName())) {
+                                } else if (migrateStorageManaged && "auth".equals(diskChildNode.getNodeName())) {
                                     diskNode.removeChild(diskChildNode);
                                 }
                             }
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
similarity index 100%
rename from plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
rename to plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java
index efb51fb..8d1ae77 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java
@@ -427,7 +427,7 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
-    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
+    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout) {
         throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration.");
     }
 
@@ -440,4 +440,9 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
     public boolean createFolder(String uuid, String path) {
         throw new UnsupportedOperationException("A folder cannot be created in this configuration.");
     }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout) {
+        return null;
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
index 91cfc4e..c1f73d7 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
@@ -394,9 +394,15 @@ public class KVMStoragePoolManager {
         return adaptor.copyPhysicalDisk(disk, name, destPool, timeout);
     }
 
-    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
+    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout) {
         StorageAdaptor adaptor = getStorageAdaptor(destPool.getType());
-        return adaptor.createDiskFromSnapshot(snapshot, snapshotName, name, destPool);
+        return adaptor.createDiskFromSnapshot(snapshot, snapshotName, name, destPool, timeout);
+    }
+
+    public KVMPhysicalDisk createDiskWithTemplateBacking(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, long size,
+                                                         KVMStoragePool destPool, int timeout) {
+        StorageAdaptor adaptor = getStorageAdaptor(destPool.getType());
+        return adaptor.createDiskFromTemplateBacking(template, name, format, size, destPool, timeout);
     }
 
 }
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 83a7a12..9a2fd27 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
@@ -36,19 +36,12 @@ import java.util.UUID;
 
 import javax.naming.ConfigurationException;
 
-import com.cloud.agent.direct.download.DirectTemplateDownloader;
-import com.cloud.agent.direct.download.DirectTemplateDownloader.DirectTemplateInformation;
-import com.cloud.agent.direct.download.HttpDirectTemplateDownloader;
-import com.cloud.agent.direct.download.MetalinkDirectTemplateDownloader;
-import com.cloud.agent.direct.download.NfsDirectTemplateDownloader;
-import com.cloud.agent.direct.download.HttpsDirectTemplateDownloader;
-import com.cloud.exception.InvalidParameterValueException;
-import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand;
+import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
 import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand;
+import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand;
 import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand;
-import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
 import org.apache.cloudstack.storage.command.AttachAnswer;
 import org.apache.cloudstack.storage.command.AttachCommand;
 import org.apache.cloudstack.storage.command.CopyCmdAnswer;
@@ -95,7 +88,14 @@ import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.DiskTO;
 import com.cloud.agent.api.to.NfsTO;
 import com.cloud.agent.api.to.S3TO;
+import com.cloud.agent.direct.download.DirectTemplateDownloader;
+import com.cloud.agent.direct.download.DirectTemplateDownloader.DirectTemplateInformation;
+import com.cloud.agent.direct.download.HttpDirectTemplateDownloader;
+import com.cloud.agent.direct.download.HttpsDirectTemplateDownloader;
+import com.cloud.agent.direct.download.MetalinkDirectTemplateDownloader;
+import com.cloud.agent.direct.download.NfsDirectTemplateDownloader;
 import com.cloud.exception.InternalErrorException;
+import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
@@ -105,6 +105,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.DeviceType;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.DiscardType;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.DiskProtocol;
 import com.cloud.storage.JavaStorageLayer;
+import com.cloud.storage.MigrationOptions;
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StorageLayer;
@@ -114,9 +115,9 @@ import com.cloud.storage.template.Processor.FormatInfo;
 import com.cloud.storage.template.QCOW2Processor;
 import com.cloud.storage.template.TemplateLocation;
 import com.cloud.utils.NumbersUtil;
-import com.cloud.utils.storage.S3.S3Utils;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.script.Script;
+import com.cloud.utils.storage.S3.S3Utils;
 
 public class KVMStorageProcessor implements StorageProcessor {
     private static final Logger s_logger = Logger.getLogger(KVMStorageProcessor.class);
@@ -1352,6 +1353,32 @@ public class KVMStorageProcessor implements StorageProcessor {
         }
     }
 
+    /**
+     * Create volume with backing file (linked clone)
+     */
+    protected KVMPhysicalDisk createLinkedCloneVolume(MigrationOptions migrationOptions, KVMStoragePool srcPool, KVMStoragePool primaryPool, VolumeObjectTO volume, PhysicalDiskFormat format, int timeout) {
+        String srcBackingFilePath = migrationOptions.getSrcBackingFilePath();
+        boolean copySrcTemplate = migrationOptions.isCopySrcTemplate();
+        KVMPhysicalDisk srcTemplate = srcPool.getPhysicalDisk(srcBackingFilePath);
+        KVMPhysicalDisk destTemplate;
+        if (copySrcTemplate) {
+            KVMPhysicalDisk copiedTemplate = storagePoolMgr.copyPhysicalDisk(srcTemplate, srcTemplate.getName(), primaryPool, 10000 * 1000);
+            destTemplate = primaryPool.getPhysicalDisk(copiedTemplate.getPath());
+        } else {
+            destTemplate = primaryPool.getPhysicalDisk(srcBackingFilePath);
+        }
+        return storagePoolMgr.createDiskWithTemplateBacking(destTemplate, volume.getUuid(), format, volume.getSize(),
+                primaryPool, timeout);
+    }
+
+    /**
+     * Create full clone volume from VM snapshot
+     */
+    protected KVMPhysicalDisk createFullCloneVolume(MigrationOptions migrationOptions, VolumeObjectTO volume, KVMStoragePool primaryPool, PhysicalDiskFormat format) {
+        s_logger.debug("For VM migration with full-clone volume: Creating empty stub disk for source disk " + migrationOptions.getSrcVolumeUuid() + " and size: " + volume.getSize() + " and format: " + format);
+        return primaryPool.createPhysicalDisk(volume.getUuid(), format, volume.getProvisioningType(), volume.getSize());
+    }
+
     @Override
     public Answer createVolume(final CreateObjectCommand cmd) {
         final VolumeObjectTO volume = (VolumeObjectTO)cmd.getData();
@@ -1369,8 +1396,23 @@ public class KVMStorageProcessor implements StorageProcessor {
             } else {
                 format = PhysicalDiskFormat.valueOf(volume.getFormat().toString().toUpperCase());
             }
-            vol = primaryPool.createPhysicalDisk(volume.getUuid(), format,
-                    volume.getProvisioningType(), disksize);
+
+            MigrationOptions migrationOptions = volume.getMigrationOptions();
+            if (migrationOptions != null) {
+                String srcStoreUuid = migrationOptions.getSrcPoolUuid();
+                StoragePoolType srcPoolType = migrationOptions.getSrcPoolType();
+                KVMStoragePool srcPool = storagePoolMgr.getStoragePool(srcPoolType, srcStoreUuid);
+                int timeout = migrationOptions.getTimeout();
+
+                if (migrationOptions.getType() == MigrationOptions.Type.LinkedClone) {
+                    vol = createLinkedCloneVolume(migrationOptions, srcPool, primaryPool, volume, format, timeout);
+                } else if (migrationOptions.getType() == MigrationOptions.Type.FullClone) {
+                    vol = createFullCloneVolume(migrationOptions, volume, primaryPool, format);
+                }
+            } else {
+                vol = primaryPool.createPhysicalDisk(volume.getUuid(), format,
+                        volume.getProvisioningType(), disksize);
+            }
 
             final VolumeObjectTO newVol = new VolumeObjectTO();
             if(vol != null) {
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
index dc8083f..f858a4f 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
@@ -95,6 +95,33 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
         return true;
     }
 
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, long size,
+                                                         KVMStoragePool destPool, int timeout) {
+        s_logger.info("Creating volume " + name + " with template backing " + template.getName() + " in pool " + destPool.getUuid() +
+                " (" + destPool.getType().toString() + ") with size " + size);
+
+        KVMPhysicalDisk disk = null;
+        String destPath = destPool.getLocalPath().endsWith("/") ?
+                destPool.getLocalPath() + name :
+                destPool.getLocalPath() + "/" + name;
+
+        if (destPool.getType() == StoragePoolType.NetworkFilesystem) {
+            try {
+                if (format == PhysicalDiskFormat.QCOW2) {
+                    QemuImg qemu = new QemuImg(timeout);
+                    QemuImgFile destFile = new QemuImgFile(destPath, format);
+                    destFile.setSize(size);
+                    QemuImgFile backingFile = new QemuImgFile(template.getPath(), template.getFormat());
+                    qemu.create(destFile, backingFile);
+                }
+            } catch (QemuImgException e) {
+                s_logger.error("Failed to create " + destPath + " due to a failed executing of qemu-img: " + e.getMessage());
+            }
+        }
+        return disk;
+    }
+
     public StorageVol getVolume(StoragePool pool, String volName) {
         StorageVol vol = null;
 
@@ -914,7 +941,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
                     case SPARSE:
                     case FAT:
                         QemuImgFile srcFile = new QemuImgFile(template.getPath(), template.getFormat());
-                        qemu.convert(srcFile, destFile, options);
+                        qemu.convert(srcFile, destFile, options, null);
                         break;
                     }
                 } else if (format == PhysicalDiskFormat.RAW) {
@@ -927,7 +954,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
                     }
                     QemuImg qemu = new QemuImg(timeout);
                     Map<String, String> options = new HashMap<String, String>();
-                    qemu.convert(sourceFile, destFile, options);
+                    qemu.convert(sourceFile, destFile, options, null);
                 }
             } catch (QemuImgException e) {
                 s_logger.error("Failed to create " + disk.getPath() +
@@ -1302,8 +1329,35 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
-    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
-        return null;
+    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout) {
+        s_logger.info("Creating volume " + name + " from snapshot " + snapshotName + " in pool " + destPool.getUuid() +
+                " (" + destPool.getType().toString() + ")");
+
+        PhysicalDiskFormat format = snapshot.getFormat();
+        long size = snapshot.getSize();
+        String destPath = destPool.getLocalPath().endsWith("/") ?
+                destPool.getLocalPath() + name :
+                destPool.getLocalPath() + "/" + name;
+
+        if (destPool.getType() == StoragePoolType.NetworkFilesystem) {
+            try {
+                if (format == PhysicalDiskFormat.QCOW2) {
+                    QemuImg qemu = new QemuImg(timeout);
+                    QemuImgFile destFile = new QemuImgFile(destPath, format);
+                    if (size > snapshot.getVirtualSize()) {
+                        destFile.setSize(size);
+                    } else {
+                        destFile.setSize(snapshot.getVirtualSize());
+                    }
+                    QemuImgFile srcFile = new QemuImgFile(snapshot.getPath(), snapshot.getFormat());
+                    qemu.convert(srcFile, destFile, snapshotName);
+                }
+            } catch (QemuImgException e) {
+                s_logger.error("Failed to create " + destPath +
+                        " due to a failed executing of qemu-img: " + e.getMessage());
+            }
+        }
+        return destPool.getPhysicalDisk(name);
     }
 
     @Override
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java
index 596582d..309308a 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java
@@ -294,7 +294,7 @@ public class ManagedNfsStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
-    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
+    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout) {
         throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration.");
     }
 
@@ -314,6 +314,11 @@ public class ManagedNfsStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
+    public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout) {
+        return null;
+    }
+
+    @Override
     public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, PhysicalDiskFormat format, ProvisioningType provisioningType, long size) {
         return null;
     }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java
index 2c1ed23..a3c1387a 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java
@@ -66,11 +66,19 @@ public interface StorageAdaptor {
 
     public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout);
 
-    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool);
+    public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout);
 
     public boolean refresh(KVMStoragePool pool);
 
     public boolean deleteStoragePool(KVMStoragePool pool);
 
     public boolean createFolder(String uuid, String path);
+
+    /**
+     * Creates disk using template backing.
+     * Precondition: Template is on destPool
+     */
+    KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template,
+                                                  String name, PhysicalDiskFormat format, long size,
+                                                  KVMStoragePool destPool, int timeout);
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
index eb1eeea..481dcdc 100644
--- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
+++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
@@ -20,17 +20,24 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.NotImplementedException;
+
 import com.cloud.storage.Storage;
 import com.cloud.utils.script.OutputInterpreter;
 import com.cloud.utils.script.Script;
-import org.apache.commons.lang.NotImplementedException;
 
 public class QemuImg {
 
     /* The qemu-img binary. We expect this to be in $PATH */
     public String _qemuImgPath = "qemu-img";
+    private String cloudQemuImgPath = "cloud-qemu-img";
     private int timeout;
 
+    private String getQemuImgPathScript = String.format("which %s >& /dev/null; " +
+                    "if [ $? -gt 0 ]; then echo \"%s\"; else echo \"%s\"; fi",
+            cloudQemuImgPath, _qemuImgPath, cloudQemuImgPath);
+
     /* Shouldn't we have KVMPhysicalDisk and LibvirtVMDef read this? */
     public static enum PhysicalDiskFormat {
         RAW("raw"), QCOW2("qcow2"), VMDK("vmdk"), FILE("file"), RBD("rbd"), SHEEPDOG("sheepdog"), HTTP("http"), HTTPS("https"), TAR("tar"), DIR("dir");
@@ -220,10 +227,18 @@ public class QemuImg {
      * @param options
      *            Options for the convert. Takes a Map<String, String> with key value
      *            pairs which are passed on to qemu-img without validation.
+     * @param snapshotName
+     *            If it is provided, convertion uses it as parameter
      * @return void
      */
-    public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, final Map<String, String> options) throws QemuImgException {
-        final Script script = new Script(_qemuImgPath, timeout);
+    public void convert(final QemuImgFile srcFile, final QemuImgFile destFile,
+                        final Map<String, String> options, final String snapshotName) throws QemuImgException {
+        Script script = new Script(_qemuImgPath, timeout);
+        if (StringUtils.isNotBlank(snapshotName)) {
+            String qemuPath = Script.runSimpleBashScript(getQemuImgPathScript);
+            script = new Script(qemuPath, timeout);
+        }
+
         script.add("convert");
         // autodetect source format. Sometime int he future we may teach KVMPhysicalDisk about more formats, then we can explicitly pass them if necessary
         //s.add("-f");
@@ -242,6 +257,13 @@ public class QemuImg {
             script.add(optionsStr);
         }
 
+        if (StringUtils.isNotBlank(snapshotName)) {
+            script.add("-f");
+            script.add(srcFile.getFormat().toString());
+            script.add("-s");
+            script.add(snapshotName);
+        }
+
         script.add(srcFile.getFileName());
         script.add(destFile.getFileName());
 
@@ -269,7 +291,26 @@ public class QemuImg {
      * @return void
      */
     public void convert(final QemuImgFile srcFile, final QemuImgFile destFile) throws QemuImgException {
-        this.convert(srcFile, destFile, null);
+        this.convert(srcFile, destFile, null, null);
+    }
+
+    /**
+     * Convert a image from source to destination
+     *
+     * This method calls 'qemu-img convert' and takes three objects
+     * as an argument.
+     *
+     *
+     * @param srcFile
+     *            The source file
+     * @param destFile
+     *            The destination file
+     * @param snapshotName
+     *            The snapshot name
+     * @return void
+     */
+    public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, String snapshotName) throws QemuImgException {
+        this.convert(srcFile, destFile, null, snapshotName);
     }
 
     /**
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
index eaab179..5289481 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
@@ -19,16 +19,22 @@
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.InputStream;
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Scanner;
 import org.apache.cloudstack.utils.linux.MemStat;
 import java.util.Map;
-import java.util.HashMap;
 import org.apache.commons.io.IOUtils;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -40,7 +46,9 @@ import org.mockito.Mockito;
 import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
+import org.xml.sax.SAXException;
 
+import com.cloud.agent.api.MigrateCommand;
 import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
 import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DiskType;
 import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DriverType;
@@ -310,6 +318,114 @@ public class LibvirtMigrateCommandWrapperTest {
         PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
     }
 
+    private static final String sourcePoolUuid = "07eb495b-5590-3877-9fb7-23c6e9a40d40";
+    private static final String destPoolUuid = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+    private static final String disk1SourceFilename = "981ab1dc-40f4-41b5-b387-6539aeddbf47";
+    private static final String disk2SourceFilename = "bf8621b3-027c-497d-963b-06319650f048";
+    private static final String sourceMultidiskDomainXml =
+            "<domain type='kvm' id='6'>\n" +
+            "  <name>i-2-3-VM</name>\n" +
+            "  <uuid>91860126-7dda-4876-ac1e-48d06cd4b2eb</uuid>\n" +
+            "  <description>Apple Mac OS X 10.6 (32-bit)</description>\n" +
+            "  <memory unit='KiB'>524288</memory>\n" +
+            "  <currentMemory unit='KiB'>524288</currentMemory>\n" +
+            "  <vcpu placement='static'>1</vcpu>\n" +
+            "  <cputune>\n" +
+            "    <shares>250</shares>\n" +
+            "  </cputune>\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'>91860126-7dda-4876-ac1e-48d06cd4b2eb</entry>\n" +
+            "    </system>\n" +
+            "  </sysinfo>\n" +
+            "  <os>\n" +
+            "    <type arch='x86_64' machine='rhel6.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>\n" +
+            "  </cpu>\n" +
+            "  <clock offset='utc'/>\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='file' device='disk'>\n" +
+            "      <driver name='qemu' type='qcow2' cache='none'/>\n" +
+            "      <source file='/mnt/07eb495b-5590-3877-9fb7-23c6e9a40d40/981ab1dc-40f4-41b5-b387-6539aeddbf47'/>\n" +
+            "      <target dev='hda' bus='ide'/>\n" +
+            "      <serial>e8141f63b5364a7f8cbb</serial>\n" +
+            "      <alias name='ide0-0-0'/>\n" +
+            "      <address type='drive' controller='0' bus='0' target='0' unit='0'/>\n" +
+            "    </disk>\n" +
+            "    <disk type='file' device='cdrom'>\n" +
+            "      <driver name='qemu' type='raw' cache='none'/>\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" +
+            "    <disk type='file' device='disk'>\n" +
+            "      <driver name='qemu' type='qcow2' cache='none'/>\n" +
+            "      <source file='/mnt/07eb495b-5590-3877-9fb7-23c6e9a40d40/bf8621b3-027c-497d-963b-06319650f048'/>\n" +
+            "      <target dev='vdb' bus='virtio'/>\n" +
+            "      <serial>bf8621b3027c497d963b</serial>\n" +
+            "      <alias name='virtio-disk1'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>\n" +
+            "    </disk>\n" +
+            "    <controller type='usb' index='0'>\n" +
+            "      <alias name='usb0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>\n" +
+            "    </controller>\n" +
+            "    <controller type='ide' index='0'>\n" +
+            "      <alias name='ide0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>\n" +
+            "    </controller>\n" +
+            "    <interface type='bridge'>\n" +
+            "      <mac address='02:00:4c:5f:00:01'/>\n" +
+            "      <source bridge='breth1-511'/>\n" +
+            "      <target dev='vnet6'/>\n" +
+            "      <model type='e1000'/>\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/2'/>\n" +
+            "      <target port='0'/>\n" +
+            "      <alias name='serial0'/>\n" +
+            "    </serial>\n" +
+            "    <console type='pty' tty='/dev/pts/2'>\n" +
+            "      <source path='/dev/pts/2'/>\n" +
+            "      <target type='serial' port='0'/>\n" +
+            "      <alias name='serial0'/>\n" +
+            "    </console>\n" +
+            "    <input type='tablet' bus='usb'>\n" +
+            "      <alias name='input0'/>\n" +
+            "    </input>\n" +
+            "    <input type='mouse' bus='ps2'/>\n" +
+            "    <graphics type='vnc' port='5902' autoport='yes' listen='10.2.2.31' passwd='LEm_y8SIs-8hXimtxnyEnA'>\n" +
+            "      <listen type='address' address='10.2.2.31'/>\n" +
+            "    </graphics>\n" +
+            "    <video>\n" +
+            "      <model type='cirrus' vram='9216' heads='1'/>\n" +
+            "      <alias name='video0'/>\n" +
+            "      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>\n" +
+            "    </video>\n" +
+            "    <memballoon model='none'>\n" +
+            "      <alias name='balloon0'/>\n" +
+            "    </memballoon>\n" +
+            "  </devices>\n" +
+            "</domain>\n";
+
     @Test
     public void testReplaceIpForVNCInDescFile() {
         final String targetIp = "192.168.22.21";
@@ -488,7 +604,7 @@ public class LibvirtMigrateCommandWrapperTest {
 
         MigrateDiskInfo diskInfo = new MigrateDiskInfo("123456", DiskType.BLOCK, DriverType.RAW, Source.FILE, "sourctest");
         mapMigrateStorage.put("/mnt/812ea6a3-7ad0-30f4-9cab-01e3f2985b98/4650a2f7-fce5-48e2-beaa-bcdf063194e6", diskInfo);
-        final String result = libvirtMigrateCmdWrapper.replaceStorage(fullfile, mapMigrateStorage);
+        final String result = libvirtMigrateCmdWrapper.replaceStorage(fullfile, mapMigrateStorage, true);
 
         InputStream in = IOUtils.toInputStream(result);
         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
@@ -499,4 +615,25 @@ public class LibvirtMigrateCommandWrapperTest {
         assertXpath(doc, "/domain/devices/disk/driver/@type", "raw");
     }
 
+    public void testReplaceStorageXmlDiskNotManagedStorage() throws ParserConfigurationException, TransformerException, SAXException, IOException {
+        final LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
+        String destDisk1FileName = "XXXXXXXXXXXXXX";
+        String destDisk2FileName = "YYYYYYYYYYYYYY";
+        String destDisk1Path = String.format("/mnt/%s/%s", destPoolUuid, destDisk1FileName);
+        MigrateCommand.MigrateDiskInfo migrateDisk1Info = new MigrateCommand.MigrateDiskInfo(disk1SourceFilename,
+                MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
+                MigrateCommand.MigrateDiskInfo.Source.FILE, destDisk1Path);
+        String destDisk2Path = String.format("/mnt/%s/%s", destPoolUuid, destDisk2FileName);
+        MigrateCommand.MigrateDiskInfo migrateDisk2Info = new MigrateCommand.MigrateDiskInfo(disk2SourceFilename,
+                MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
+                MigrateCommand.MigrateDiskInfo.Source.FILE, destDisk2Path);
+        Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
+        migrateStorage.put(disk1SourceFilename, migrateDisk1Info);
+        migrateStorage.put(disk2SourceFilename, migrateDisk2Info);
+        String newXml = lw.replaceStorage(sourceMultidiskDomainXml, migrateStorage, false);
+        assertTrue(newXml.contains(destDisk1Path));
+        assertTrue(newXml.contains(destDisk2Path));
+        assertFalse(newXml.contains("/mnt/" + sourcePoolUuid + "/" + disk1SourceFilename));
+        assertFalse(newXml.contains("/mnt/" + sourcePoolUuid + "/" + disk2SourceFilename));
+    }
 }
diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java
index 87cf779..ea0c4eb 100644
--- a/server/src/main/java/com/cloud/configuration/Config.java
+++ b/server/src/main/java/com/cloud/configuration/Config.java
@@ -890,14 +890,6 @@ public enum Config {
             "0",
             "Default disk I/O writerate in bytes per second allowed in User vm's disk.",
             null),
-    KvmAutoConvergence(
-            "Advanced",
-            ManagementServer.class,
-            Boolean.class,
-            "kvm.auto.convergence",
-            "false",
-            "Setting this to 'true' allows KVM to use auto convergence to complete VM migration (libvirt version 1.2.3+ and QEMU version 1.6+)",
-            null),
     ControlCidr(
             "Advanced",
             ManagementServer.class,
diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
index ae7df65..79343ab 100644
--- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
@@ -2489,8 +2489,17 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] { StorageCleanupInterval, StorageCleanupDelay, StorageCleanupEnabled, TemplateCleanupEnabled,
-                KvmStorageOfflineMigrationWait, KvmStorageOnlineMigrationWait, MaxNumberOfManagedClusteredFileSystems, PRIMARY_STORAGE_DOWNLOAD_WAIT};
+        return new ConfigKey<?>[]{
+                StorageCleanupInterval,
+                StorageCleanupDelay,
+                StorageCleanupEnabled,
+                TemplateCleanupEnabled,
+                KvmStorageOfflineMigrationWait,
+                KvmStorageOnlineMigrationWait,
+                KvmAutoConvergence,
+                MaxNumberOfManagedClusteredFileSystems,
+                PRIMARY_STORAGE_DOWNLOAD_WAIT
+        };
     }
 
     @Override
diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
index 9cba758..9beeb7b 100755
--- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
+++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
@@ -577,6 +577,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
         if (vm.getIsoId() != null) {
             Map<Volume, StoragePool> storageForDisks = dest.getStorageForDisks();
             Long poolId = null;
+            TemplateInfo template;
             if (MapUtils.isNotEmpty(storageForDisks)) {
                 for (StoragePool storagePool : storageForDisks.values()) {
                     if (poolId != null && storagePool.getId() != poolId) {
@@ -584,8 +585,11 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
                     }
                     poolId = storagePool.getId();
                 }
+                template = prepareIso(vm.getIsoId(), vm.getDataCenterId(), dest.getHost().getId(), poolId);
+            } else {
+                template = _tmplFactory.getTemplate(vm.getIsoId(), DataStoreRole.Primary, dest.getDataCenter().getId());
             }
-            TemplateInfo template = prepareIso(vm.getIsoId(), vm.getDataCenterId(), dest.getHost().getId(), poolId);
+
             if (template == null){
                 s_logger.error("Failed to prepare ISO on secondary or cache storage");
                 throw new CloudRuntimeException("Failed to prepare ISO on secondary or cache storage");
diff --git a/test/integration/smoke/test_snapshots.py b/test/integration/smoke/test_snapshots.py
index 6e9a877..7b67637 100644
--- a/test/integration/smoke/test_snapshots.py
+++ b/test/integration/smoke/test_snapshots.py
@@ -309,9 +309,9 @@ class TestSnapshotRootDisk(cloudstackTestCase):
 
         # Migrate volume to new Primary Storage
         Volume.migrate(self.apiclient,
-            storageid=storage.id,
-            volumeid=vol.id
-        )
+                       storageid=storage.id,
+                       volumeid=vol.id
+                       )
 
         volume_response = list_volumes(
             self.apiclient,
diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py
index b7a996f..23aa161 100644
--- a/test/integration/smoke/test_vm_life_cycle.py
+++ b/test/integration/smoke/test_vm_life_cycle.py
@@ -24,10 +24,10 @@ from marvin.cloudstackAPI import (recoverVirtualMachine,
                                   detachIso,
                                   provisionCertificate,
                                   updateConfiguration,
-                                  migrateVirtualMachine)
-from marvin.lib.utils import (cleanup_resources,
-                              validateList,
-                              SshClient)
+                                  migrateVirtualMachine,
+                                  migrateVirtualMachineWithVolume)
+from marvin.lib.utils import *
+
 from marvin.lib.base import (Account,
                              ServiceOffering,
                              VirtualMachine,
@@ -1309,3 +1309,207 @@ class TestMigrateVMwithVolume(cloudstackTestCase):
         vol = Volume.list(self.apiclient, volume=volume1.id)[0]
 
         self.assertEqual(vol.storageid, target_pool.id, "Storage pool was not the same as expected")
+
+
+class TestKVMLiveMigration(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestKVMLiveMigration, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls._cleanup = []
+
+        # Get Zone, Domain and templates
+        domain = get_domain(cls.apiclient)
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.services['mode'] = cls.zone.networktype
+        cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][
+            0].__dict__
+        cls.management_ip = cls.config.__dict__["mgtSvr"][0].__dict__["mgtSvrIp"]
+
+        template = get_template(
+            cls.apiclient,
+            cls.zone.id,
+            cls.services["ostype"]
+        )
+        if template == FAILED:
+            assert False, "get_template() failed to return template with description %s" % cls.services["ostype"]
+
+        # Set Zones and disk offerings
+        cls.services["small"]["zoneid"] = cls.zone.id
+        cls.services["small"]["template"] = template.id
+
+        cls.services["iso1"]["zoneid"] = cls.zone.id
+
+        # Create VMs, NAT Rules etc
+        cls.account = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            domainid=domain.id
+        )
+
+        cls.small_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["small"]
+        )
+
+        cls._cleanup = [
+            cls.small_offering,
+            cls.account
+        ]
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.apiclient = super(TestKVMLiveMigration, cls).getClsTestClient().getApiClient()
+        try:
+            cleanup_resources(cls.apiclient, cls._cleanup)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+
+        if self.hypervisor.lower() not in ["kvm"]:
+            self.skipTest("VM Live Migration with Volumes is not supported on other than KVM")
+
+        self.hosts = Host.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            type='Routing',
+            hypervisor='KVM')
+
+        if len(self.hosts) < 2:
+            self.skipTest("Requires at least two hosts for performing migration related tests")
+
+    def tearDown(self):
+        try:
+            cleanup_resources(self.apiclient, self.cleanup)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+
+    def get_target_host(self, virtualmachineid):
+        target_hosts = Host.listForMigration(self.apiclient,
+                                             virtualmachineid=virtualmachineid)
+        if len(target_hosts) < 1:
+            self.skipTest("No target hosts found")
+
+        return target_hosts[0]
+
+    def get_target_pool(self, volid):
+        target_pools = StoragePool.listForMigration(self.apiclient, id=volid)
+
+        if len(target_pools) < 1:
+            self.skipTest("Not enough storage pools found")
+
+        return target_pools[0]
+
+    def get_vm_volumes(self, id):
+        return Volume.list(self.apiclient, virtualmachineid=id, listall=True)
+
+    def deploy_vm(self):
+        return VirtualMachine.create(
+            self.apiclient,
+            self.services["small"],
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.small_offering.id,
+            mode=self.services["mode"])
+
+    def create_volume(self):
+        small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0]
+
+        return Volume.create(
+            self.apiclient,
+            self.services,
+            account=self.account.name,
+            diskofferingid=small_disk_offering.id,
+            domainid=self.account.domainid,
+            zoneid=self.zone.id
+        )
+
+    @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg", "security"], required_hardware="false")
+    def test_01_migrate_VM_and_root_volume(self):
+        """Test VM will be migrated with it's root volume"""
+        # Validates the following:
+        # 1. Deploys a VM
+        # 2. Migrates the VM and the root volume to another host and storage pool
+        # 3. Asserts migration success and checks for location
+
+        vm = self.deploy_vm()
+
+        root_volume = self.get_vm_volumes(vm.id)[0]
+
+        target_pool = self.get_target_pool(root_volume.id)
+
+        target_host = self.get_target_host(vm.id)
+
+        cmd = migrateVirtualMachineWithVolume.migrateVirtualMachineWithVolumeCmd()
+
+        cmd.migrateto = [{"volume": str(root_volume.id), "pool": str(target_pool.id)}]
+
+        cmd.virtualmachineid = vm.id
+        cmd.hostid = target_host.id
+
+        response = self.apiclient.migrateVirtualMachineWithVolume(cmd)
+
+        self.assertEqual(response.hostid, target_host.id)
+
+        self.assertEqual(Volume.list(self.apiclient, id=root_volume.id)[0].storageid,
+                         target_pool.id,
+                         "Pool ID was not as expected")
+
+    @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg", "security"], required_hardware="false")
+    def test_02_migrate_VM_with_two_data_disks(self):
+        """Test VM will be migrated with it's root volume"""
+        # Validate the following
+        # 1. Deploys a VM and attaches 2 data disks
+        # 2. Finds suitable host for migration
+        # 3. Finds suitable storage pool for volumes
+        # 4. Migrate the VM to new host and storage pool and assert migration successful
+
+        vm = self.deploy_vm()
+
+        volume1 = self.create_volume()
+        volume2 = self.create_volume()
+
+        vm.attach_volume(self.apiclient, volume1)
+        vm.attach_volume(self.apiclient, volume2)
+
+        root_volume = self.get_vm_volumes(vm.id)[0]
+
+        target_pool = self.get_target_pool(root_volume.id)
+        volume1.target_pool = self.get_target_pool(volume1.id)
+        volume2.target_pool = self.get_target_pool(volume2.id)
+
+        target_host = self.get_target_host(vm.id)
+
+        cmd = migrateVirtualMachineWithVolume.migrateVirtualMachineWithVolumeCmd()
+
+        cmd.migrateto = [{"volume": str(root_volume.id), "pool": str(target_pool.id)},
+                         {"volume": str(volume1.id), "pool": str(volume1.target_pool.id)},
+                         {"volume": str(volume2.id), "pool": str(volume2.target_pool.id)}]
+        cmd.virtualmachineid = vm.id
+        cmd.hostid = target_host.id
+
+        response = self.apiclient.migrateVirtualMachineWithVolume(cmd)
+
+        self.assertEqual(Volume.list(self.apiclient, id=root_volume.id)[0].storageid,
+                         target_pool.id,
+                         "Pool ID not as expected")
+
+        self.assertEqual(Volume.list(self.apiclient, id=volume1.id)[0].storageid,
+                         volume1.target_pool.id,
+                         "Pool ID not as expected")
+
+        self.assertEqual(Volume.list(self.apiclient, id=volume2.id)[0].storageid,
+                         volume2.target_pool.id,
+                         "Pool ID not as expected")
+
+        self.assertEqual(response.hostid,
+                         target_host.id,
+                         "HostID not as expected")
+


Mime
View raw message