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: CLOUDSTACK-9620: Enhancements for managed storage (#2298)
Date Sun, 14 Jan 2018 18:35:59 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 a30a31c  CLOUDSTACK-9620: Enhancements for managed storage (#2298)
a30a31c is described below

commit a30a31c9b7f9b5fe246a02285bf820e7fe9ce16e
Author: Mike Tutkowski <mike.tutkowski@netapp.com>
AuthorDate: Sun Jan 14 11:35:52 2018 -0700

    CLOUDSTACK-9620: Enhancements for managed storage (#2298)
    
    Allowed zone-wide primary storage based on a custom plug-in to be added via the GUI in a KVM-only environment (previously this only worked for XenServer and VMware)
    
    Added support for root disks on managed storage with KVM
    
    Added support for volume snapshots with managed storage on KVM
    
    Enable creating a template directly from a volume (i.e. without having to go through a volume snapshot) on KVM with managed storage
    
    Only allow the resizing of a volume for managed storage on KVM if the volume in question is either not attached to a VM or is attached to a VM in the Stopped state.
    
    Included support for Reinstall VM on KVM with managed storage
    
    Enabled offline migration on KVM from non-managed storage to managed storage and vice versa
    
    Included support for online storage migration on KVM with managed storage (NFS and Ceph to managed storage)
    
    Added support to download (extract) a managed-storage volume to a QCOW2 file
    
    When uploading a file from outside of CloudStack to CloudStack, set the min and max IOPS, if applicable.
    
    Included support for the KVM auto-convergence feature
    
    The compression flag was actually added in version 1.0.3 (1000003) as opposed to version 1.3.0 (1003000) (changed this to reflect the correct version)
    
    On KVM when using iSCSI-based managed storage, if the user shuts a VM down from the guest OS (as opposed to doing so from CloudStack), we need to pass to the KVM agent a list of applicable iSCSI volumes that need to be disconnected.
    
    Added a new Global Setting: kvm.storage.live.migration.wait
    
    For XenServer, added a check to enforce that only volumes from zone-wide managed storage can be storage motioned from a host in one cluster to a host in another cluster (cannot do so at the time being with volumes from cluster-scoped managed storage)
    
    Don’t allow Storage XenMotion on a VM that has any managed-storage volume with one or more snapshots.
    
    Enabled for managed storage with VMware: Template caching, create snapshot, delete snapshot, create volume from snapshot, and create template from snapshot
    
    Added an SIOC API plug-in to support VMware SIOC
    
    When starting a VM that uses managed storage in a cluster other than the one it last was running in, we need to remove the reference to the iSCSI volume from the original cluster.
    
    Added the ability to revert a volume to a snapshot
    
    Enabled cluster-scoped managed storage
    
    Added support for VMware dynamic discovery
---
 api/src/com/cloud/agent/api/to/DiskTO.java         |    4 +
 .../api/command/user/volume/ResizeVolumeCmd.java   |    8 +
 client/pom.xml                                     |   15 +
 core/src/com/cloud/agent/api/MigrateCommand.java   |   96 +-
 .../com/cloud/agent/api/ModifyTargetsAnswer.java   |   11 +
 .../com/cloud/agent/api/ModifyTargetsCommand.java  |   31 +
 .../agent/api/PrepareForMigrationCommand.java      |   11 +-
 core/src/com/cloud/agent/api/StartAnswer.java      |   17 +-
 core/src/com/cloud/agent/api/StopCommand.java      |   17 +
 .../cloud/agent/api/storage/CopyVolumeCommand.java |   35 +-
 .../agent/api/storage/MigrateVolumeCommand.java    |   38 +-
 .../cloud/storage/template/TemplateLocation.java   |    1 +
 .../cloudstack/storage/command/CopyCommand.java    |    4 +-
 .../api/storage/DataStoreCapabilities.java         |    6 +-
 .../src/com/cloud/storage/StorageManager.java      |   58 +-
 engine/orchestration/pom.xml                       |    5 +
 .../com/cloud/vm/VirtualMachineManagerImpl.java    |  185 +-
 .../engine/orchestration/VolumeOrchestrator.java   |   47 +-
 .../motion/StorageSystemDataMotionStrategy.java    | 1783 +++++++++++++++++---
 .../snapshot/StorageSystemSnapshotStrategy.java    |  478 +++++-
 .../storage/volume/VolumeServiceImpl.java          |   72 +-
 .../SolidFireIntegrationTestManagerImpl.java       |    3 +
 .../solidfire/SolidFireIntegrationTestUtil.java    |   30 +-
 plugins/api/vmware-sioc/pom.xml                    |   47 +
 .../cloudstack/vmware-sioc/module.properties       |   18 +
 .../cloudstack/vmware-sioc/spring-sioc-context.xml |   33 +
 .../api/command/admin/sioc/UpdateSiocInfoCmd.java  |  105 ++
 .../response/sioc/ApiUpdateSiocInfoResponse.java   |   30 +-
 .../apache/cloudstack/api/sioc/ApiSiocService.java |    7 +-
 .../cloudstack/api/sioc/ApiSiocServiceImpl.java    |   31 +-
 .../org/apache/cloudstack/sioc/SiocManager.java    |    8 +-
 .../apache/cloudstack/sioc/SiocManagerImpl.java    |  463 +++++
 .../src/org/apache/cloudstack/util/LoginInfo.java  |   31 +-
 .../apache/cloudstack/util/vmware/VMwareUtil.java  |  570 +++++++
 .../kvm/resource/LibvirtComputingResource.java     |    4 +
 .../hypervisor/kvm/resource/MigrateKVMAsync.java   |   27 +-
 .../wrapper/LibvirtCopyVolumeCommandWrapper.java   |   85 +-
 .../LibvirtDeleteStoragePoolCommandWrapper.java    |   12 +-
 .../wrapper/LibvirtMigrateCommandWrapper.java      |  189 ++-
 .../LibvirtMigrateVolumeCommandWrapper.java        |   95 ++
 .../LibvirtModifyTargetsCommandWrapper.java        |   80 +
 .../LibvirtPrepareForMigrationCommandWrapper.java  |   18 +-
 .../wrapper/LibvirtStopCommandWrapper.java         |   21 +-
 .../kvm/storage/IscsiAdmStorageAdaptor.java        |   89 +-
 .../kvm/storage/KVMStoragePoolManager.java         |   12 +
 .../kvm/storage/KVMStorageProcessor.java           |  193 ++-
 .../kvm/storage/LibvirtStorageAdaptor.java         |    6 +
 .../kvm/storage/ManagedNfsStorageAdaptor.java      |    5 +
 .../hypervisor/kvm/storage/StorageAdaptor.java     |    2 +
 .../hypervisor/vmware/resource/VmwareResource.java |  228 ++-
 .../storage/resource/VmwareStorageProcessor.java   | 1691 +++++++++++++++----
 .../xenserver/resource/CitrixResourceBase.java     |    9 +-
 .../resource/XenServerStorageProcessor.java        |    8 +-
 .../resource/Xenserver625StorageProcessor.java     |   19 +-
 .../wrapper/xenbase/CitrixStartCommandWrapper.java |   17 +-
 .../motion/XenServerStorageMotionStrategy.java     |   40 +
 plugins/pom.xml                                    |   11 +
 .../driver/SolidFirePrimaryDataStoreDriver.java    |  256 ++-
 .../SolidFirePrimaryDataStoreLifeCycle.java        |   75 +-
 .../SolidFireSharedPrimaryDataStoreLifeCycle.java  |   95 +-
 .../datastore/provider/SolidFireHostListener.java  |   15 +-
 .../provider/SolidFireSharedHostListener.java      |   18 +-
 .../storage/datastore/util/SolidFireUtil.java      |   39 +-
 .../com/cloud/api/query/ViewResponseHelper.java    |    4 +-
 server/src/com/cloud/configuration/Config.java     |    9 +-
 .../src/com/cloud/server/ManagementServerImpl.java |   34 +-
 .../src/com/cloud/storage/StorageManagerImpl.java  |   81 +-
 .../com/cloud/storage/VolumeApiServiceImpl.java    |  149 +-
 .../storage/snapshot/SnapshotManagerImpl.java      |   22 +-
 server/src/com/cloud/vm/UserVmManagerImpl.java     |  142 +-
 .../storage/snapshot/SnapshotManagerTest.java      |   26 +-
 .../plugins/solidfire/TestAddRemoveHosts.py        |   23 +-
 .../plugins/solidfire/TestManagedSystemVMs.py      |   12 +-
 .../integration/plugins/solidfire/TestSnapshots.py |  287 ++--
 .../plugins/solidfire/TestUploadDownload.py        |  516 ++++++
 .../solidfire/TestVMMigrationWithStorage.py        |   10 +-
 .../plugins/solidfire/TestVMSnapshots.py           |   19 +-
 test/integration/plugins/solidfire/TestVolumes.py  | 1377 +++++++++++++--
 test/integration/plugins/solidfire/util/sf_util.py |   82 +
 tools/apidoc/gen_toc.py                            |    3 +-
 tools/marvin/marvin/lib/base.py                    |   54 +-
 ui/scripts/system.js                               |    4 +
 .../cloud/hypervisor/vmware/mo/DatastoreMO.java    |   18 +-
 .../vmware/mo/HostDatastoreSystemMO.java           |   32 +
 .../hypervisor/vmware/mo/HostStorageSystemMO.java  |    8 +
 .../vmware/mo/VirtualMachineDiskInfoBuilder.java   |    3 +-
 .../hypervisor/vmware/mo/VirtualMachineMO.java     |   79 +-
 87 files changed, 9182 insertions(+), 1469 deletions(-)

diff --git a/api/src/com/cloud/agent/api/to/DiskTO.java b/api/src/com/cloud/agent/api/to/DiskTO.java
index f982844..7b3d10b 100644
--- a/api/src/com/cloud/agent/api/to/DiskTO.java
+++ b/api/src/com/cloud/agent/api/to/DiskTO.java
@@ -27,6 +27,7 @@ public class DiskTO {
     public static final String CHAP_INITIATOR_SECRET = "chapInitiatorSecret";
     public static final String CHAP_TARGET_USERNAME = "chapTargetUsername";
     public static final String CHAP_TARGET_SECRET = "chapTargetSecret";
+    public static final String SCSI_NAA_DEVICE_ID = "scsiNaaDeviceId";
     public static final String MANAGED = "managed";
     public static final String IQN = "iqn";
     public static final String STORAGE_HOST = "storageHost";
@@ -36,6 +37,9 @@ public class DiskTO {
     public static final String PROTOCOL_TYPE = "protocoltype";
     public static final String PATH = "path";
     public static final String UUID = "uuid";
+    public static final String VMDK = "vmdk";
+    public static final String EXPAND_DATASTORE = "expandDatastore";
+    public static final String TEMPLATE_RESIGN = "templateResign";
 
     private DataTO data;
     private Long diskSeq;
diff --git a/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java b/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
index 4ec9449..8eea632 100644
--- a/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
+++ b/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
@@ -78,6 +78,14 @@ public class ResizeVolumeCmd extends BaseAsyncCmd {
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
 
+    public ResizeVolumeCmd() {}
+
+    public ResizeVolumeCmd(Long id, Long minIops, Long maxIops) {
+        this.id = id;
+        this.minIops = minIops;
+        this.maxIops = maxIops;
+    }
+
     //TODO use the method getId() instead of this one.
     public Long getEntityId() {
         return id;
diff --git a/client/pom.xml b/client/pom.xml
index cfc1f87..2d6a2f8 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -1152,6 +1152,21 @@
       </dependencies>
     </profile>
     <profile>
+        <id>vmwaresioc</id>
+        <activation>
+            <property>
+                <name>noredist</name>
+            </property>
+        </activation>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.cloudstack</groupId>
+                <artifactId>cloud-plugin-api-vmware-sioc</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </profile>
+    <profile>
       <id>quickcloud</id>
       <activation>
         <property>
diff --git a/core/src/com/cloud/agent/api/MigrateCommand.java b/core/src/com/cloud/agent/api/MigrateCommand.java
index 9d1f83a..3e7dfc1 100644
--- a/core/src/com/cloud/agent/api/MigrateCommand.java
+++ b/core/src/com/cloud/agent/api/MigrateCommand.java
@@ -19,15 +19,20 @@
 
 package com.cloud.agent.api;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import com.cloud.agent.api.to.VirtualMachineTO;
 
 public class MigrateCommand extends Command {
-    String vmName;
-    String destIp;
-    String hostGuid;
-    boolean isWindows;
-    VirtualMachineTO vmTO;
-    boolean executeInSequence = false;
+    private String vmName;
+    private String destIp;
+    private Map<String, MigrateDiskInfo> migrateStorage;
+    private boolean autoConvergence;
+    private String hostGuid;
+    private boolean isWindows;
+    private VirtualMachineTO vmTO;
+    private boolean executeInSequence = false;
 
     protected MigrateCommand() {
     }
@@ -40,6 +45,22 @@ public class MigrateCommand extends Command {
         this.executeInSequence = executeInSequence;
     }
 
+    public void setMigrateStorage(Map<String, MigrateDiskInfo> migrateStorage) {
+        this.migrateStorage = migrateStorage;
+    }
+
+    public Map<String, MigrateDiskInfo> getMigrateStorage() {
+        return migrateStorage != null ? new HashMap<>(migrateStorage) : new HashMap<String, MigrateDiskInfo>();
+    }
+
+    public void setAutoConvergence(boolean autoConvergence) {
+        this.autoConvergence = autoConvergence;
+    }
+
+    public boolean isAutoConvergence() {
+        return autoConvergence;
+    }
+
     public boolean isWindows() {
         return isWindows;
     }
@@ -68,4 +89,67 @@ public class MigrateCommand extends Command {
     public boolean executeInSequence() {
         return executeInSequence;
     }
+
+    public static class MigrateDiskInfo {
+        public enum DiskType {
+            FILE, BLOCK;
+
+            @Override
+            public String toString() {
+                return name().toLowerCase();
+            }
+        }
+
+        public enum DriverType {
+            QCOW2, RAW;
+
+            @Override
+            public String toString() {
+                return name().toLowerCase();
+            }
+        }
+
+        public enum Source {
+            FILE, DEV;
+
+            @Override
+            public String toString() {
+                return name().toLowerCase();
+            }
+        }
+
+        private final String serialNumber;
+        private final DiskType diskType;
+        private final DriverType driverType;
+        private final Source source;
+        private final String sourceText;
+
+        public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText) {
+            this.serialNumber = serialNumber;
+            this.diskType = diskType;
+            this.driverType = driverType;
+            this.source = source;
+            this.sourceText = sourceText;
+        }
+
+        public String getSerialNumber() {
+            return serialNumber;
+        }
+
+        public DiskType getDiskType() {
+            return diskType;
+        }
+
+        public DriverType getDriverType() {
+            return driverType;
+        }
+
+        public Source getSource() {
+            return source;
+        }
+
+        public String getSourceText() {
+            return sourceText;
+        }
+    }
 }
diff --git a/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java b/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
index c192e4a..0c2afcf 100644
--- a/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
+++ b/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
@@ -19,5 +19,16 @@
 
 package com.cloud.agent.api;
 
+import java.util.List;
+
 public class ModifyTargetsAnswer extends Answer {
+    private List<String> connectedPaths;
+
+    public void setConnectedPaths(List<String> connectedPaths) {
+        this.connectedPaths = connectedPaths;
+    }
+
+    public List<String> getConnectedPaths() {
+        return connectedPaths;
+    }
 }
diff --git a/core/src/com/cloud/agent/api/ModifyTargetsCommand.java b/core/src/com/cloud/agent/api/ModifyTargetsCommand.java
index 424d797..9f4935b 100644
--- a/core/src/com/cloud/agent/api/ModifyTargetsCommand.java
+++ b/core/src/com/cloud/agent/api/ModifyTargetsCommand.java
@@ -23,7 +23,11 @@ import java.util.List;
 import java.util.Map;
 
 public class ModifyTargetsCommand extends Command {
+    public enum TargetTypeToRemove { BOTH, NEITHER, STATIC, DYNAMIC }
+
     public static final String IQN = "iqn";
+    public static final String STORAGE_TYPE = "storageType";
+    public static final String STORAGE_UUID = "storageUuid";
     public static final String STORAGE_HOST = "storageHost";
     public static final String STORAGE_PORT = "storagePort";
     public static final String CHAP_NAME = "chapName";
@@ -32,6 +36,9 @@ public class ModifyTargetsCommand extends Command {
     public static final String MUTUAL_CHAP_SECRET = "mutualChapSecret";
 
     private boolean add;
+    private boolean applyToAllHostsInCluster;
+    private TargetTypeToRemove targetTypeToRemove = TargetTypeToRemove.BOTH;
+    private boolean removeAsync;
     private List<Map<String, String>> targets;
 
     public void setAdd(boolean add) {
@@ -42,6 +49,30 @@ public class ModifyTargetsCommand extends Command {
         return add;
     }
 
+    public void setApplyToAllHostsInCluster(boolean applyToAllHostsInCluster) {
+        this.applyToAllHostsInCluster = applyToAllHostsInCluster;
+    }
+
+    public boolean getApplyToAllHostsInCluster() {
+        return applyToAllHostsInCluster;
+    }
+
+    public void setTargetTypeToRemove(TargetTypeToRemove targetTypeToRemove) {
+        this.targetTypeToRemove = targetTypeToRemove;
+    }
+
+    public TargetTypeToRemove getTargetTypeToRemove() {
+        return targetTypeToRemove;
+    }
+
+    public void setRemoveAsync(boolean removeAsync) {
+        this.removeAsync = removeAsync;
+    }
+
+    public boolean isRemoveAsync() {
+        return removeAsync;
+    }
+
     public void setTargets(List<Map<String, String>> targets) {
         this.targets = targets;
     }
diff --git a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java b/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
index 6b89654..a2c4f67 100644
--- a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
+++ b/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
@@ -22,7 +22,8 @@ package com.cloud.agent.api;
 import com.cloud.agent.api.to.VirtualMachineTO;
 
 public class PrepareForMigrationCommand extends Command {
-    VirtualMachineTO vm;
+    private VirtualMachineTO vm;
+    private boolean rollback;
 
     protected PrepareForMigrationCommand() {
     }
@@ -35,6 +36,14 @@ public class PrepareForMigrationCommand extends Command {
         return vm;
     }
 
+    public void setRollback(boolean rollback) {
+        this.rollback = rollback;
+    }
+
+    public boolean isRollback() {
+        return rollback;
+    }
+
     @Override
     public boolean executeInSequence() {
         return true;
diff --git a/core/src/com/cloud/agent/api/StartAnswer.java b/core/src/com/cloud/agent/api/StartAnswer.java
index 6f63f20..d5d0e83 100644
--- a/core/src/com/cloud/agent/api/StartAnswer.java
+++ b/core/src/com/cloud/agent/api/StartAnswer.java
@@ -24,9 +24,16 @@ import java.util.Map;
 import com.cloud.agent.api.to.VirtualMachineTO;
 
 public class StartAnswer extends Answer {
+    public static final String PATH = "path";
+    public static final String IMAGE_FORMAT = "imageFormat";
+
     VirtualMachineTO vm;
     String hostGuid;
-    Map<String, String> _iqnToPath;
+    // key = an applicable IQN (ex. iqn.1998-01.com.vmware.iscsi:name1)
+    // value = a Map with the following data:
+    //   key = PATH or IMAGE_FORMAT (defined above)
+    //   value = Example if PATH is key: UUID of VDI; Example if IMAGE_FORMAT is key: DiskTO.VHD
+    private Map<String, Map<String, String>> _iqnToData;
 
     protected StartAnswer() {
     }
@@ -61,11 +68,11 @@ public class StartAnswer extends Answer {
         return hostGuid;
     }
 
-    public void setIqnToPath(Map<String, String> iqnToPath) {
-        _iqnToPath = iqnToPath;
+    public void setIqnToData(Map<String, Map<String, String>> iqnToData) {
+        _iqnToData = iqnToData;
     }
 
-    public Map<String, String> getIqnToPath() {
-        return _iqnToPath;
+    public Map<String, Map<String, String>> getIqnToData() {
+        return _iqnToData;
     }
 }
diff --git a/core/src/com/cloud/agent/api/StopCommand.java b/core/src/com/cloud/agent/api/StopCommand.java
index e1d68f8..eedf736 100644
--- a/core/src/com/cloud/agent/api/StopCommand.java
+++ b/core/src/com/cloud/agent/api/StopCommand.java
@@ -22,6 +22,10 @@ package com.cloud.agent.api;
 import com.cloud.agent.api.to.GPUDeviceTO;
 import com.cloud.vm.VirtualMachine;
 
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+
 public class StopCommand extends RebootCommand {
     private boolean isProxy = false;
     private String urlPort = null;
@@ -30,6 +34,11 @@ public class StopCommand extends RebootCommand {
     boolean checkBeforeCleanup = false;
     String controlIp = null;
     boolean forceStop = false;
+    /**
+     * On KVM when using iSCSI-based managed storage, if the user shuts a VM down from the guest OS (as opposed to doing so from CloudStack),
+     * we need to pass to the KVM agent a list of applicable iSCSI volumes that need to be disconnected.
+     */
+    private List<Map<String, String>> volumesToDisconnect = new ArrayList<>();
 
     protected StopCommand() {
     }
@@ -102,4 +111,12 @@ public class StopCommand extends RebootCommand {
     public boolean isForceStop() {
         return forceStop;
     }
+
+    public void setVolumesToDisconnect(List<Map<String, String>> volumesToDisconnect) {
+        this.volumesToDisconnect = volumesToDisconnect;
+    }
+
+    public List<Map<String, String>> getVolumesToDisconnect() {
+        return volumesToDisconnect;
+    }
 }
diff --git a/core/src/com/cloud/agent/api/storage/CopyVolumeCommand.java b/core/src/com/cloud/agent/api/storage/CopyVolumeCommand.java
index a75e9ba..863a9ca 100644
--- a/core/src/com/cloud/agent/api/storage/CopyVolumeCommand.java
+++ b/core/src/com/cloud/agent/api/storage/CopyVolumeCommand.java
@@ -19,18 +19,22 @@
 
 package com.cloud.agent.api.storage;
 
+import java.util.Map;
+
+import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.storage.StoragePool;
 
 public class CopyVolumeCommand extends StorageNfsVersionCommand {
-
-    long volumeId;
-    String volumePath;
-    StorageFilerTO pool;
-    String secondaryStorageURL;
-    boolean toSecondaryStorage;
-    String vmName;
-    boolean executeInSequence = false;
+    private long volumeId;
+    private String volumePath;
+    private StorageFilerTO pool;
+    private String secondaryStorageURL;
+    private boolean toSecondaryStorage;
+    private String vmName;
+    private DataTO srcData;
+    private Map<String, String> srcDetails;
+    private boolean executeInSequence;
 
     public CopyVolumeCommand() {
     }
@@ -75,4 +79,19 @@ public class CopyVolumeCommand extends StorageNfsVersionCommand {
         return vmName;
     }
 
+    public void setSrcData(DataTO srcData) {
+        this.srcData = srcData;
+    }
+
+    public DataTO getSrcData() {
+        return srcData;
+    }
+
+    public void setSrcDetails(Map<String, String> srcDetails) {
+        this.srcDetails = srcDetails;
+    }
+
+    public Map<String, String> getSrcDetails() {
+        return srcDetails;
+    }
 }
diff --git a/core/src/com/cloud/agent/api/storage/MigrateVolumeCommand.java b/core/src/com/cloud/agent/api/storage/MigrateVolumeCommand.java
index b409944..77430c3 100644
--- a/core/src/com/cloud/agent/api/storage/MigrateVolumeCommand.java
+++ b/core/src/com/cloud/agent/api/storage/MigrateVolumeCommand.java
@@ -19,19 +19,26 @@
 
 package com.cloud.agent.api.storage;
 
+import java.util.Map;
+
 import com.cloud.agent.api.Command;
+import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.Volume;
 
 public class MigrateVolumeCommand extends Command {
-
     long volumeId;
     String volumePath;
     StorageFilerTO pool;
     String attachedVmName;
     Volume.Type volumeType;
 
+    private DataTO srcData;
+    private DataTO destData;
+    private Map<String, String> srcDetails;
+    private Map<String, String> destDetails;
+
     public MigrateVolumeCommand(long volumeId, String volumePath, StoragePool pool, int timeout) {
         this.volumeId = volumeId;
         this.volumePath = volumePath;
@@ -48,6 +55,15 @@ public class MigrateVolumeCommand extends Command {
         this.setWait(timeout);
     }
 
+    public MigrateVolumeCommand(DataTO srcData, DataTO destData, Map<String, String> srcDetails, Map<String, String> destDetails, int timeout) {
+        this.srcData = srcData;
+        this.destData = destData;
+        this.srcDetails = srcDetails;
+        this.destDetails = destDetails;
+
+        setWait(timeout);
+    }
+
     @Override
     public boolean executeInSequence() {
         return true;
@@ -72,4 +88,24 @@ public class MigrateVolumeCommand extends Command {
     public Volume.Type getVolumeType() {
         return volumeType;
     }
+
+    public DataTO getSrcData() {
+        return srcData;
+    }
+
+    public DataTO getDestData() {
+        return destData;
+    }
+
+    public Map<String, String> getSrcDetails() {
+        return srcDetails;
+    }
+
+    public Map<String, String> getDestDetails() {
+        return destDetails;
+    }
+
+    public int getWaitInMillSeconds() {
+        return getWait() * 1000;
+    }
 }
\ No newline at end of file
diff --git a/core/src/com/cloud/storage/template/TemplateLocation.java b/core/src/com/cloud/storage/template/TemplateLocation.java
index d10d05a..c10acc1 100644
--- a/core/src/com/cloud/storage/template/TemplateLocation.java
+++ b/core/src/com/cloud/storage/template/TemplateLocation.java
@@ -205,6 +205,7 @@ public class TemplateLocation {
         }
 
         _props.setProperty("virtualsize", Long.toString(newInfo.virtualSize));
+        _props.setProperty("size", Long.toString(newInfo.size));
         _formats.add(newInfo);
         return true;
     }
diff --git a/core/src/org/apache/cloudstack/storage/command/CopyCommand.java b/core/src/org/apache/cloudstack/storage/command/CopyCommand.java
index e7ebab8..aac082a 100644
--- a/core/src/org/apache/cloudstack/storage/command/CopyCommand.java
+++ b/core/src/org/apache/cloudstack/storage/command/CopyCommand.java
@@ -29,8 +29,8 @@ public class CopyCommand extends StorageSubSystemCommand {
     private DataTO destTO;
     private DataTO cacheTO;
     private boolean executeInSequence = false;
-    private Map<String, String> options = new HashMap<String, String>();
-    private Map<String, String> options2 = new HashMap<String, String>();
+    private Map<String, String> options = new HashMap<>();
+    private Map<String, String> options2 = new HashMap<>();
 
     public CopyCommand(final DataTO srcData, final DataTO destData, final int timeout, final boolean executeInSequence) {
         super();
diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java
index 2cde5bd..f537d8f 100644
--- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java
+++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java
@@ -36,5 +36,9 @@ public enum DataStoreCapabilities {
     /**
      * indicates that this driver supports the "cloneOfSnapshot" property of cloud.snapshot_details (for creating a volume from a volume)
      */
-    CAN_CREATE_VOLUME_FROM_VOLUME
+    CAN_CREATE_VOLUME_FROM_VOLUME,
+    /**
+     * indicates that this driver supports reverting a volume to a snapshot state
+     */
+    CAN_REVERT_VOLUME_TO_SNAPSHOT
 }
diff --git a/engine/components-api/src/com/cloud/storage/StorageManager.java b/engine/components-api/src/com/cloud/storage/StorageManager.java
index d314fb3..530a7de 100644
--- a/engine/components-api/src/com/cloud/storage/StorageManager.java
+++ b/engine/components-api/src/com/cloud/storage/StorageManager.java
@@ -44,14 +44,54 @@ import com.cloud.vm.DiskProfile;
 import com.cloud.vm.VMInstanceVO;
 
 public interface StorageManager extends StorageService {
-    static final ConfigKey<Integer> StorageCleanupInterval = new ConfigKey<Integer>(Integer.class, "storage.cleanup.interval", "Advanced", "86400",
-            "The interval (in seconds) to wait before running the storage cleanup thread.", false, ConfigKey.Scope.Global, null);
-    static final ConfigKey<Integer> StorageCleanupDelay = new ConfigKey<Integer>(Integer.class, "storage.cleanup.delay", "Advanced", "86400",
-            "Determines how long (in seconds) to wait before actually expunging destroyed volumes. The default value = the default value of storage.cleanup.interval.", false, ConfigKey.Scope.Global, null);
-    static final ConfigKey<Boolean> StorageCleanupEnabled = new ConfigKey<Boolean>(Boolean.class, "storage.cleanup.enabled", "Advanced", "true",
-            "Enables/disables the storage cleanup thread.", false, ConfigKey.Scope.Global, null);
-    static final ConfigKey<Boolean> TemplateCleanupEnabled = new ConfigKey<Boolean>(Boolean.class, "storage.template.cleanup.enabled", "Storage", "true",
-            "Enable/disable template cleanup activity, only take effect when overall storage cleanup is enabled", false, ConfigKey.Scope.Global, null);
+    ConfigKey<Integer> StorageCleanupInterval = new ConfigKey<>(Integer.class,
+            "storage.cleanup.interval",
+            "Advanced",
+            "86400",
+            "The interval (in seconds) to wait before running the storage cleanup thread.",
+            false,
+            ConfigKey.Scope.Global,
+            null);
+    ConfigKey<Integer> StorageCleanupDelay = new ConfigKey<>(Integer.class,
+            "storage.cleanup.delay",
+            "Advanced",
+            "86400",
+            "Determines how long (in seconds) to wait before actually expunging destroyed volumes. The default value = the default value of storage.cleanup.interval.",
+            false,
+            ConfigKey.Scope.Global,
+            null);
+    ConfigKey<Boolean> StorageCleanupEnabled = new ConfigKey<>(Boolean.class,
+            "storage.cleanup.enabled",
+            "Advanced",
+            "true",
+            "Enables/disables the storage cleanup thread.",
+            false,
+            ConfigKey.Scope.Global,
+            null);
+    ConfigKey<Boolean> TemplateCleanupEnabled = new ConfigKey<>(Boolean.class,
+            "storage.template.cleanup.enabled",
+            "Storage",
+            "true",
+            "Enable/disable template cleanup activity, only take effect when overall storage cleanup is enabled",
+            false,
+            ConfigKey.Scope.Global,
+            null);
+    ConfigKey<Integer> KvmStorageOfflineMigrationWait = new ConfigKey<>(Integer.class,
+            "kvm.storage.offline.migration.wait",
+            "Storage",
+            "10800",
+            "Timeout in seconds for offline (non-live) storage migration to complete on KVM",
+            true,
+            ConfigKey.Scope.Global,
+            null);
+    ConfigKey<Integer> KvmStorageOnlineMigrationWait = new ConfigKey<>(Integer.class,
+            "kvm.storage.online.migration.wait",
+            "Storage",
+            "10800",
+            "Timeout in seconds for online (live) storage migration to complete on KVM (migrateVirtualMachineWithVolume)",
+            true,
+            ConfigKey.Scope.Global,
+            null);
 
     /**
      * Returns a comma separated list of tags for the specified storage pool
@@ -102,6 +142,8 @@ public interface StorageManager extends StorageService {
 
     Host updateSecondaryStorage(long secStorageId, String newUrl);
 
+    void removeStoragePoolFromCluster(long hostId, String iScsiName, StoragePool storagePool);
+
     List<Long> getUpHostsInPool(long poolId);
 
     void cleanupSecondaryStorage(boolean recurring);
diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml
index c69e29c..dab3c38 100755
--- a/engine/orchestration/pom.xml
+++ b/engine/orchestration/pom.xml
@@ -58,6 +58,11 @@
       <artifactId>cloud-utils</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
index 9916728..375b091 100755
--- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -39,6 +39,7 @@ import java.util.concurrent.TimeUnit;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
@@ -85,6 +86,7 @@ import com.cloud.agent.api.ClusterVMMetaDataSyncAnswer;
 import com.cloud.agent.api.ClusterVMMetaDataSyncCommand;
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.MigrateCommand;
+import com.cloud.agent.api.ModifyTargetsCommand;
 import com.cloud.agent.api.PingRoutingCommand;
 import com.cloud.agent.api.PlugNicAnswer;
 import com.cloud.agent.api.PlugNicCommand;
@@ -114,6 +116,7 @@ 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;
@@ -536,6 +539,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
 
         final Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId();
 
+        List<Map<String, String>> targets = getTargets(hostId, vm.getId());
+
         if (volumeExpungeCommands != null && volumeExpungeCommands.size() > 0 && hostId != null) {
             final Commands cmds = new Commands(Command.OnError.Stop);
 
@@ -563,6 +568,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
         // Clean up volumes based on the vm's instance id
         volumeMgr.cleanupVolumes(vm.getId());
 
+        if (hostId != null && CollectionUtils.isNotEmpty(targets)) {
+            removeDynamicTargets(hostId, targets);
+        }
+
         final VirtualMachineGuru guru = getVmGuru(vm);
         guru.finalizeExpunge(vm);
         //remove the overcommit detials from the uservm details
@@ -599,6 +608,64 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
 
     }
 
+    private List<Map<String, String>> getTargets(Long hostId, long vmId) {
+        List<Map<String, String>> targets = new ArrayList<>();
+
+        HostVO hostVO = _hostDao.findById(hostId);
+
+        if (hostVO == null || hostVO.getHypervisorType() != HypervisorType.VMware) {
+            return targets;
+        }
+
+        List<VolumeVO> volumes = _volsDao.findByInstance(vmId);
+
+        if (CollectionUtils.isEmpty(volumes)) {
+            return targets;
+        }
+
+        for (VolumeVO volume : volumes) {
+            StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId());
+
+            if (storagePoolVO != null && storagePoolVO.isManaged()) {
+                Map<String, String> target = new HashMap<>();
+
+                target.put(ModifyTargetsCommand.STORAGE_HOST, storagePoolVO.getHostAddress());
+                target.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePoolVO.getPort()));
+                target.put(ModifyTargetsCommand.IQN, volume.get_iScsiName());
+
+                targets.add(target);
+            }
+        }
+
+        return targets;
+    }
+
+    private void removeDynamicTargets(long hostId, List<Map<String, String>> targets) {
+        ModifyTargetsCommand cmd = new ModifyTargetsCommand();
+
+        cmd.setTargets(targets);
+        cmd.setApplyToAllHostsInCluster(true);
+        cmd.setAdd(false);
+        cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
+
+        sendModifyTargetsCommand(cmd, hostId);
+    }
+
+    private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
+        Answer answer = _agentMgr.easySend(hostId, cmd);
+
+        if (answer == null) {
+            String msg = "Unable to get an answer to the modify targets command";
+
+            s_logger.warn(msg);
+        }
+        else if (!answer.getResult()) {
+            String msg = "Unable to modify target on the following host: " + hostId;
+
+            s_logger.warn(msg);
+        }
+    }
+
     @Override
     public boolean start() {
         // TODO, initial delay is hardcoded
@@ -1073,8 +1140,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
 
                     startAnswer = cmds.getAnswer(StartAnswer.class);
                     if (startAnswer != null && startAnswer.getResult()) {
-                        handlePath(vmTO.getDisks(), startAnswer.getIqnToPath());
+                        handlePath(vmTO.getDisks(), startAnswer.getIqnToData());
+
                         final String host_guid = startAnswer.getHost_guid();
+
                         if (host_guid != null) {
                             final HostVO finalHost = _resourceMgr.findHostByGuid(host_guid);
                             if (finalHost == null) {
@@ -1254,8 +1323,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
     }
 
     // for managed storage on XenServer and VMware, need to update the DB with a path if the VDI/VMDK file was newly created
-    private void handlePath(final DiskTO[] disks, final Map<String, String> iqnToPath) {
-        if (disks != null && iqnToPath != null) {
+    private void handlePath(final DiskTO[] disks, final Map<String, Map<String, String>> iqnToData) {
+        if (disks != null && iqnToData != null) {
             for (final DiskTO disk : disks) {
                 final Map<String, String> details = disk.getDetails();
                 final boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED));
@@ -1264,12 +1333,31 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
                     final Long volumeId = disk.getData().getId();
                     final VolumeVO volume = _volsDao.findById(volumeId);
                     final String iScsiName = volume.get_iScsiName();
-                    final String path = iqnToPath.get(iScsiName);
 
-                    if (path != null) {
-                        volume.setPath(path);
+                    boolean update = false;
+
+                    final Map<String, String> data = iqnToData.get(iScsiName);
+
+                    if (data != null) {
+                        final String path = data.get(StartAnswer.PATH);
+
+                        if (path != null) {
+                            volume.setPath(path);
 
-                        _volsDao.update(volumeId, volume);
+                            update = true;
+                        }
+
+                        final String imageFormat = data.get(StartAnswer.IMAGE_FORMAT);
+
+                        if (imageFormat != null) {
+                            volume.setFormat(ImageFormat.valueOf(imageFormat));
+
+                            update = true;
+                        }
+
+                        if (update) {
+                            _volsDao.update(volumeId, volume);
+                        }
                     }
                 }
             }
@@ -1331,10 +1419,37 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
         }
     }
 
+    private List<Map<String, String>> getVolumesToDisconnect(VirtualMachine vm) {
+        List<Map<String, String>> volumesToDisconnect = new ArrayList<>();
+
+        List<VolumeVO> volumes = _volsDao.findByInstance(vm.getId());
+
+        if (CollectionUtils.isEmpty(volumes)) {
+            return volumesToDisconnect;
+        }
+
+        for (VolumeVO volume : volumes) {
+            StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
+
+            if (storagePool != null && storagePool.isManaged()) {
+                Map<String, String> info = new HashMap<>(3);
+
+                info.put(DiskTO.STORAGE_HOST, storagePool.getHostAddress());
+                info.put(DiskTO.STORAGE_PORT, String.valueOf(storagePool.getPort()));
+                info.put(DiskTO.IQN, volume.get_iScsiName());
+
+                volumesToDisconnect.add(info);
+            }
+        }
+
+        return volumesToDisconnect;
+    }
+
     protected boolean sendStop(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final boolean force, final boolean checkBeforeCleanup) {
         final VirtualMachine vm = profile.getVirtualMachine();
         StopCommand stpCmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup);
         stpCmd.setControlIp(getControlNicIpForVM(vm));
+        stpCmd.setVolumesToDisconnect(getVolumesToDisconnect(vm));
         final StopCommand stop = stpCmd;
         try {
             Answer answer = null;
@@ -2103,6 +2218,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
         try {
             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);
+
+            mc.setAutoConvergence(kvmAutoConvergence);
+
             mc.setHostGuid(dest.getHost().getGuid());
 
             try {
@@ -2176,32 +2297,48 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
         final Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<>();
 
         for (final VolumeVO volume : allVolumes) {
-            final Long poolId = volumeToPool.get(Long.valueOf(volume.getId()));
+            final Long poolId = volumeToPool.get(volume.getId());
             final StoragePoolVO destPool = _storagePoolDao.findById(poolId);
             final StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId());
             final DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
 
             if (destPool != null) {
-                // Check if pool is accessible from the destination host and disk offering with which the volume was
-                // created is compliant with the pool type.
-                if (_poolHostDao.findByPoolHost(destPool.getId(), host.getId()) == null || destPool.isLocal() != diskOffering.getUseLocalStorage()) {
-                    // Cannot find a pool for the volume. Throw an exception.
-                    throw new CloudRuntimeException("Cannot migrate volume " + volume + " to storage pool " + destPool + " while migrating vm to host " + host +
-                            ". Either the pool is not accessible from the host or because of the offering with which the volume is created it cannot be placed on " +
-                            "the given pool.");
-                } else if (destPool.getId() == currentPool.getId()) {
-                    // If the pool to migrate to is the same as current pool, the volume doesn't need to be migrated.
-                } else {
-                    volumeToPoolObjectMap.put(volume, destPool);
+                if (currentPool.isManaged()) {
+                    if (destPool.getId() == currentPool.getId()) {
+                        volumeToPoolObjectMap.put(volume, currentPool);
+                    }
+                    else {
+                        throw new CloudRuntimeException("Currently, a volume on managed storage can only be 'migrated' to itself.");
+                    }
+                }
+                else {
+                    // Check if pool is accessible from the destination host and disk offering with which the volume was
+                    // created is compliant with the pool type.
+                    if (_poolHostDao.findByPoolHost(destPool.getId(), host.getId()) == null || destPool.isLocal() != diskOffering.getUseLocalStorage()) {
+                        // Cannot find a pool for the volume. Throw an exception.
+                        throw new CloudRuntimeException("Cannot migrate volume " + volume + " to storage pool " + destPool + " while migrating vm to host " + host +
+                                ". Either the pool is not accessible from the host or because of the offering with which the volume is created it cannot be placed on " +
+                                "the given pool.");
+                    } else if (destPool.getId() == currentPool.getId()) {
+                        // If the pool to migrate to is the same as current pool, the volume doesn't need to be migrated.
+                    } else {
+                        volumeToPoolObjectMap.put(volume, destPool);
+                    }
                 }
             } else {
                 if (currentPool.isManaged()) {
-                    volumeToPoolObjectMap.put(volume, currentPool);
+                    if (currentPool.getScope() == ScopeType.ZONE) {
+                        volumeToPoolObjectMap.put(volume, currentPool);
+                    }
+                    else {
+                        throw new CloudRuntimeException("Currently, you can only 'migrate' a volume on managed storage if its storage pool is zone wide.");
+                    }
                 } else {
                     // Find a suitable pool for the volume. Call the storage pool allocator to find the list of pools.
 
                     final DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType());
-                    final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), host.getId(), null, null);
+                    final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(),
+                            host.getId(), null, null);
 
                     final List<StoragePool> poolList = new ArrayList<>();
                     final ExcludeList avoid = new ExcludeList();
@@ -3588,6 +3725,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
         try {
             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);
+
+            mc.setAutoConvergence(kvmAutoConvergence);
+
             mc.setHostGuid(dest.getHost().getGuid());
 
             try {
diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
index eccbfd6..f02fdc4 100644
--- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
+++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
@@ -610,16 +610,23 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
     @Override
     public boolean volumeInactive(Volume volume) {
         Long vmId = volume.getInstanceId();
-        if (vmId != null) {
-            UserVm vm = _entityMgr.findById(UserVm.class, vmId);
-            if (vm == null) {
-                return true;
-            }
-            State state = vm.getState();
-            if (state.equals(State.Stopped) || state.equals(State.Destroyed)) {
-                return true;
-            }
+
+        if (vmId == null) {
+            return true;
+        }
+
+        UserVm vm = _entityMgr.findById(UserVm.class, vmId);
+
+        if (vm == null) {
+            return true;
+        }
+
+        State state = vm.getState();
+
+        if (state.equals(State.Stopped) || state.equals(State.Destroyed)) {
+            return true;
         }
+
         return false;
     }
 
@@ -1274,8 +1281,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
         VolumeInfo volume = volFactory.getVolume(newVol.getId(), destPool);
         Long templateId = newVol.getTemplateId();
         for (int i = 0; i < 2; i++) {
-            // retry one more time in case of template reload is required for Vmware case
-            AsyncCallFuture<VolumeApiResult> future = null;
+            // retry one more time in case of template reload is required for VMware case
+            AsyncCallFuture<VolumeApiResult> future;
+
             if (templateId == null) {
                 DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId());
                 HypervisorType hyperType = vm.getVirtualMachine().getHypervisorType();
@@ -1368,23 +1376,34 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
 
         List<VolumeTask> tasks = getTasks(vols, dest.getStorageForDisks(), vm);
         Volume vol = null;
-        StoragePool pool = null;
+        StoragePool pool;
         for (VolumeTask task : tasks) {
             if (task.type == VolumeTaskType.NOP) {
                 pool = (StoragePool)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary);
+
+                if (task.pool != null && task.pool.isManaged()) {
+                    long hostId = vm.getVirtualMachine().getHostId();
+                    Host host = _hostDao.findById(hostId);
+
+                    volService.grantAccess(volFactory.getVolume(task.volume.getId()), host, (DataStore)pool);
+                }
+
                 vol = task.volume;
+
                 // For a zone-wide managed storage, it is possible that the VM can be started in another
-                // cluster. In that case make sure that the volume in in the right access group cluster.
+                // cluster. In that case, make sure that the volume is in the right access group.
                 if (pool.isManaged()) {
                     long oldHostId = vm.getVirtualMachine().getLastHostId();
                     long hostId = vm.getVirtualMachine().getHostId();
+
                     if (oldHostId != hostId) {
                         Host oldHost = _hostDao.findById(oldHostId);
                         Host host = _hostDao.findById(hostId);
                         DataStore storagePool = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
 
+                        storageMgr.removeStoragePoolFromCluster(oldHostId, vol.get_iScsiName(), pool);
+
                         volService.revokeAccess(volFactory.getVolume(vol.getId()), oldHost, storagePool);
-                        volService.grantAccess(volFactory.getVolume(vol.getId()), host, storagePool);
                     }
                 }
             } else if (task.type == VolumeTaskType.MIGRATE) {
diff --git a/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
index 2b72290..30cff85 100644
--- a/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
+++ b/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
@@ -19,6 +19,16 @@
 package org.apache.cloudstack.storage.motion;
 
 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.ModifyTargetsAnswer;
+import com.cloud.agent.api.ModifyTargetsCommand;
+import com.cloud.agent.api.PrepareForMigrationCommand;
+import com.cloud.agent.api.storage.MigrateVolumeAnswer;
+import com.cloud.agent.api.storage.MigrateVolumeCommand;
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.DiskTO;
@@ -33,24 +43,35 @@ import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
 import com.cloud.host.dao.HostDetailsDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.server.ManagementService;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.GuestOSCategoryDao;
+import com.cloud.storage.dao.GuestOSDao;
 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.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.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;
@@ -81,46 +102,58 @@ 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.commons.lang.math.NumberUtils;
 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.concurrent.ExecutionException;
+import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 @Component
 public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
     private static final Logger LOGGER = Logger.getLogger(StorageSystemDataMotionStrategy.class);
     private static final Random RANDOM = new Random(System.nanoTime());
+    private static final int LOCK_TIME_IN_SECONDS = 300;
+    private static final String OPERATION_NOT_SUPPORTED = "This operation is not supported.";
 
     @Inject private AgentManager _agentMgr;
     @Inject private ConfigurationDao _configDao;
     @Inject private DataStoreManager dataStoreMgr;
     @Inject private DiskOfferingDao _diskOfferingDao;
+    @Inject private GuestOSCategoryDao _guestOsCategoryDao;
+    @Inject private GuestOSDao _guestOsDao;
     @Inject private ClusterDao clusterDao;
     @Inject private HostDao _hostDao;
     @Inject private HostDetailsDao hostDetailsDao;
-    @Inject private ManagementService _mgr;
     @Inject private PrimaryDataStoreDao _storagePoolDao;
     @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDataStoreDao _snapshotDataStoreDao;
     @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+    @Inject private VMInstanceDao _vmDao;
+    @Inject private VMTemplateDao _vmTemplateDao;
     @Inject private VolumeDao _volumeDao;
     @Inject private VolumeDataFactory _volumeDataFactory;
     @Inject private VolumeDetailsDao volumeDetailsDao;
     @Inject private VolumeService _volumeService;
     @Inject private StorageCacheManager cacheMgr;
     @Inject private EndPointSelector selector;
+
     @Override
     public StrategyPriority canHandle(DataObject srcData, DataObject destData) {
         if (srcData instanceof SnapshotInfo) {
@@ -136,9 +169,40 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             return StrategyPriority.HIGHEST;
         }
 
+        if (srcData instanceof VolumeInfo && destData instanceof VolumeInfo) {
+            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
+
+            if (isVolumeOnManagedStorage(srcVolumeInfo)) {
+                return StrategyPriority.HIGHEST;
+            }
+
+            VolumeInfo destVolumeInfo = (VolumeInfo)destData;
+
+            if (isVolumeOnManagedStorage(destVolumeInfo)) {
+                return StrategyPriority.HIGHEST;
+            }
+        }
+
+        if (srcData instanceof VolumeInfo && destData instanceof TemplateInfo) {
+            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
+
+            if (isVolumeOnManagedStorage(srcVolumeInfo)) {
+                return StrategyPriority.HIGHEST;
+            }
+        }
+
         return StrategyPriority.CANT_HANDLE;
     }
 
+    private boolean isVolumeOnManagedStorage(VolumeInfo volumeInfo) {
+        long storagePooldId = volumeInfo.getDataStore().getId();
+        StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePooldId);
+
+        return storagePoolVO.isManaged();
+    }
+
+    // canHandle returns true if the storage driver for the DataObject that's passed in can support certain features (what features we
+    // care about during a particular invocation of this method depend on what type of DataObject was passed in (ex. VolumeInfo versus SnapshotInfo)).
     private boolean canHandle(DataObject dataObject) {
         Preconditions.checkArgument(dataObject != null, "Passing 'null' to dataObject of canHandle(DataObject) is not supported.");
 
@@ -151,7 +215,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
                 return false;
             }
 
-            if (dataObject instanceof VolumeInfo || dataObject instanceof  SnapshotInfo) {
+            if (dataObject instanceof VolumeInfo || dataObject instanceof SnapshotInfo) {
                 String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
                 Boolean supportsStorageSystemSnapshots = Boolean.valueOf(value);
 
@@ -170,7 +234,6 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 
                     return true;
                 }
-
             }
         }
 
@@ -179,123 +242,320 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 
     @Override
     public StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
+        if (HypervisorType.KVM.equals(srcHost.getHypervisorType())) {
+            Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
+
+            for (VolumeInfo volumeInfo : volumeInfoSet) {
+                StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
+
+                if (storagePoolVO.isManaged()) {
+                    return StrategyPriority.HIGHEST;
+                }
+            }
+
+            Collection<DataStore> dataStores = volumeMap.values();
+
+            for (DataStore dataStore : dataStores) {
+                StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
+
+                if (storagePoolVO.isManaged()) {
+                    return StrategyPriority.HIGHEST;
+                }
+            }
+        }
+
         return StrategyPriority.CANT_HANDLE;
     }
 
     @Override
     public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
         if (srcData instanceof SnapshotInfo) {
-            SnapshotInfo snapshotInfo = (SnapshotInfo)srcData;
+            SnapshotInfo srcSnapshotInfo = (SnapshotInfo)srcData;
 
-            validate(snapshotInfo);
+            handleCopyAsyncForSnapshot(srcSnapshotInfo, destData, callback);
+        } else if (srcData instanceof TemplateInfo && destData instanceof VolumeInfo) {
+            TemplateInfo srcTemplateInfo = (TemplateInfo)srcData;
+            VolumeInfo destVolumeInfo = (VolumeInfo)destData;
 
-            boolean canHandleSrc = canHandle(srcData);
+            handleCopyAsyncForTemplateAndVolume(srcTemplateInfo, destVolumeInfo, callback);
+        } else if (srcData instanceof VolumeInfo && destData instanceof VolumeInfo) {
+            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
+            VolumeInfo destVolumeInfo = (VolumeInfo)destData;
 
-            if (canHandleSrc && (destData instanceof TemplateInfo || destData instanceof SnapshotInfo) &&
-                    (destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
-                handleCopyDataToSecondaryStorage(snapshotInfo, destData, callback);
+            handleCopyAsyncForVolumes(srcVolumeInfo, destVolumeInfo, callback);
+        } else if (srcData instanceof VolumeInfo && destData instanceof TemplateInfo &&
+                (destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
+            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
+            TemplateInfo destTemplateInfo = (TemplateInfo)destData;
 
-                return;
-            }
+            handleCreateTemplateFromVolume(srcVolumeInfo, destTemplateInfo, callback);
+        }
+        else {
+            handleError(OPERATION_NOT_SUPPORTED, callback);
+        }
+    }
 
-            if (destData instanceof VolumeInfo) {
-                VolumeInfo volumeInfo = (VolumeInfo)destData;
+    private void handleCopyAsyncForSnapshot(SnapshotInfo srcSnapshotInfo, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
+        verifyFormat(srcSnapshotInfo);
 
-                boolean canHandleDest = canHandle(destData);
+        boolean canHandleSrc = canHandle(srcSnapshotInfo);
 
-                if (canHandleSrc && canHandleDest) {
-                    if (snapshotInfo.getDataStore().getId() == volumeInfo.getDataStore().getId()) {
-                        handleCreateVolumeFromSnapshotBothOnStorageSystem(snapshotInfo, volumeInfo, callback);
-                        return;
-                    }
-                    else {
-                        String errMsg = "This operation is not supported (DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT " +
-                                "not supported by source or destination storage plug-in). " + getSrcDestDataStoreMsg(srcData, destData);
+        if (canHandleSrc && (destData instanceof TemplateInfo || destData instanceof SnapshotInfo) &&
+                (destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
+            handleCopyDataToSecondaryStorage(srcSnapshotInfo, destData, callback);
+        } else if (destData instanceof VolumeInfo) {
+            handleCopyAsyncForSnapshotToVolume(srcSnapshotInfo, (VolumeInfo)destData, callback);
+        } else {
+            handleError(OPERATION_NOT_SUPPORTED, callback);
+        }
+    }
 
-                        LOGGER.warn(errMsg);
+    private void handleCopyAsyncForSnapshotToVolume(SnapshotInfo srcSnapshotInfo, VolumeInfo destVolumeInfo,
+                                                    AsyncCompletionCallback<CopyCommandResult> callback) {
+        boolean canHandleDest = canHandle(destVolumeInfo);
 
-                        throw new UnsupportedOperationException(errMsg);
-                    }
-                }
+        if (!canHandleDest) {
+            handleError(OPERATION_NOT_SUPPORTED, callback);
+        }
 
-                if (canHandleDest) {
-                    handleCreateVolumeFromSnapshotOnSecondaryStorage(snapshotInfo, volumeInfo, callback);
+        boolean canHandleSrc = canHandle(srcSnapshotInfo);
 
-                    return;
-                }
+        if (!canHandleSrc) {
+            handleCreateVolumeFromSnapshotOnSecondaryStorage(srcSnapshotInfo, destVolumeInfo, callback);
+        }
+
+        if (srcSnapshotInfo.getDataStore().getId() == destVolumeInfo.getDataStore().getId()) {
+            handleCreateVolumeFromSnapshotBothOnStorageSystem(srcSnapshotInfo, destVolumeInfo, callback);
+        } else {
+            String errMsg = "To perform this operation, the source and destination primary storages must be the same.";
 
-                if (canHandleSrc) {
-                    String errMsg = "This operation is not supported (DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT " +
-                            "not supported by source storage plug-in). " + getSrcDataStoreMsg(srcData);
+            handleError(errMsg, callback);
+        }
+    }
 
-                    LOGGER.warn(errMsg);
+    private void handleCopyAsyncForTemplateAndVolume(TemplateInfo srcTemplateInfo, VolumeInfo destVolumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
+        boolean canHandleSrc = canHandle(srcTemplateInfo);
 
-                    throw new UnsupportedOperationException(errMsg);
-                }
-            }
-        } else if (srcData instanceof TemplateInfo && destData instanceof VolumeInfo) {
-            boolean canHandleSrc = canHandle(srcData);
+        if (!canHandleSrc) {
+            handleError(OPERATION_NOT_SUPPORTED, callback);
+        }
+
+        handleCreateVolumeFromTemplateBothOnStorageSystem(srcTemplateInfo, destVolumeInfo, callback);
+    }
 
-            if (!canHandleSrc) {
-                String errMsg = "This operation is not supported (DataStoreCapabilities.STORAGE_CAN_CREATE_VOLUME_FROM_VOLUME " +
-                        "not supported by destination storage plug-in). " + getDestDataStoreMsg(destData);
+    private void handleCopyAsyncForVolumes(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
+        if (srcVolumeInfo.getState() == Volume.State.Migrating) {
+            if (isVolumeOnManagedStorage(srcVolumeInfo)) {
+                if (destVolumeInfo.getDataStore().getRole() == DataStoreRole.Image || destVolumeInfo.getDataStore().getRole() == DataStoreRole.ImageCache) {
+                    handleVolumeCopyFromManagedStorageToSecondaryStorage(srcVolumeInfo, destVolumeInfo, callback);
+                } else if (!isVolumeOnManagedStorage(destVolumeInfo)) {
+                    handleVolumeMigrationFromManagedStorageToNonManagedStorage(srcVolumeInfo, destVolumeInfo, callback);
+                } else {
+                    String errMsg = "The source volume to migrate and the destination volume are both on managed storage. " +
+                            "Migration in this case is not yet supported.";
 
-                LOGGER.warn(errMsg);
+                    handleError(errMsg, callback);
+                }
+            } else if (!isVolumeOnManagedStorage(destVolumeInfo)) {
+                String errMsg = "The 'StorageSystemDataMotionStrategy' does not support this migration use case.";
 
-                throw new UnsupportedOperationException(errMsg);
+                handleError(errMsg, callback);
+            } else {
+                handleVolumeMigrationFromNonManagedStorageToManagedStorage(srcVolumeInfo, destVolumeInfo, callback);
             }
+        } else if (srcVolumeInfo.getState() == Volume.State.Uploaded &&
+                (srcVolumeInfo.getDataStore().getRole() == DataStoreRole.Image || srcVolumeInfo.getDataStore().getRole() == DataStoreRole.ImageCache) &&
+                destVolumeInfo.getDataStore().getRole() == DataStoreRole.Primary) {
+            ImageFormat imageFormat = destVolumeInfo.getFormat();
 
-            handleCreateVolumeFromTemplateBothOnStorageSystem((TemplateInfo)srcData, (VolumeInfo)destData, callback);
+            if (!ImageFormat.QCOW2.equals(imageFormat)) {
+                String errMsg = "The 'StorageSystemDataMotionStrategy' does not support this upload use case (non KVM).";
+
+                handleError(errMsg, callback);
+            }
 
-            return;
+            handleCreateVolumeFromVolumeOnSecondaryStorage(srcVolumeInfo, destVolumeInfo, destVolumeInfo.getDataCenterId(), HypervisorType.KVM, callback);
+        } else {
+            handleError(OPERATION_NOT_SUPPORTED, callback);
         }
+    }
+
+    private void handleError(String errMsg, AsyncCompletionCallback<CopyCommandResult> callback) {
+        LOGGER.warn(errMsg);
+
+        invokeCallback(errMsg, callback);
 
-        throw new UnsupportedOperationException("This operation is not supported.");
+        throw new UnsupportedOperationException(errMsg);
     }
 
-    private String getSrcDestDataStoreMsg(DataObject srcData, DataObject destData) {
-        Preconditions.checkArgument(srcData != null, "Passing 'null' to srcData of getSrcDestDataStoreMsg(DataObject, DataObject) is not supported.");
-        Preconditions.checkArgument(destData != null, "Passing 'null' to destData of getSrcDestDataStoreMsg(DataObject, DataObject) is not supported.");
+    private void invokeCallback(String errMsg, AsyncCompletionCallback<CopyCommandResult> callback) {
+        CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
+
+        CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+        result.setResult(errMsg);
 
-        return "Source data store = " + srcData.getDataStore().getName() + "; " + "Destination data store = " + destData.getDataStore().getName() + ".";
+        callback.complete(result);
     }
 
-    private String getSrcDataStoreMsg(DataObject srcData) {
-        Preconditions.checkArgument(srcData != null, "Passing 'null' to srcData of getSrcDataStoreMsg(DataObject) is not supported.");
+    private void handleVolumeCopyFromManagedStorageToSecondaryStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
+                                                                      AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+        String volumePath = null;
+
+        try {
+            if (!ImageFormat.QCOW2.equals(srcVolumeInfo.getFormat())) {
+                throw new CloudRuntimeException("Currently, only the KVM hypervisor type is supported for the migration of a volume " +
+                        "from managed storage to non-managed storage.");
+            }
+
+            HypervisorType hypervisorType = HypervisorType.KVM;
+            VirtualMachine vm = srcVolumeInfo.getAttachedVM();
+
+            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
+                throw new CloudRuntimeException("Currently, if a volume to copy from managed storage to secondary storage is attached to " +
+                        "a VM, the VM must be in the Stopped state.");
+            }
+
+            long srcStoragePoolId = srcVolumeInfo.getPoolId();
+            StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(srcStoragePoolId);
+
+            HostVO hostVO;
+
+            if (srcStoragePoolVO.getClusterId() != null) {
+                hostVO = getHostInCluster(srcStoragePoolVO.getClusterId());
+            }
+            else {
+                hostVO = getHost(srcVolumeInfo.getDataCenterId(), hypervisorType, false);
+            }
+
+            volumePath = copyVolumeToSecondaryStorage(srcVolumeInfo, destVolumeInfo, hostVO,
+                    "Unable to copy the volume from managed storage to secondary storage");
+        }
+        catch (Exception ex) {
+            errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeCopyFromManagedStorageToSecondaryStorage': " +
+                    ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            CopyCmdAnswer copyCmdAnswer;
+
+            if (errMsg != null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+            else if (volumePath == null) {
+                copyCmdAnswer = new CopyCmdAnswer("Unable to acquire a volume path");
+            }
+            else {
+                VolumeObjectTO volumeObjectTO = (VolumeObjectTO)destVolumeInfo.getTO();
+
+                volumeObjectTO.setPath(volumePath);
+
+                copyCmdAnswer = new CopyCmdAnswer(volumeObjectTO);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
 
-        return "Source data store = " + srcData.getDataStore().getName() + ".";
+            callback.complete(result);
+        }
     }
 
-    private String getDestDataStoreMsg(DataObject destData) {
-        Preconditions.checkArgument(destData != null, "Passing 'null' to destData of getDestDataStoreMsg(DataObject) is not supported.");
+    private void handleVolumeMigrationFromManagedStorageToNonManagedStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
+                                                                            AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+
+        try {
+            if (!ImageFormat.QCOW2.equals(srcVolumeInfo.getFormat())) {
+                throw new CloudRuntimeException("Currently, only the KVM hypervisor type is supported for the migration of a volume " +
+                        "from managed storage to non-managed storage.");
+            }
+
+            HypervisorType hypervisorType = HypervisorType.KVM;
+            VirtualMachine vm = srcVolumeInfo.getAttachedVM();
+
+            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
+                throw new CloudRuntimeException("Currently, if a volume to migrate from managed storage to non-managed storage is attached to " +
+                        "a VM, the VM must be in the Stopped state.");
+            }
+
+            long destStoragePoolId = destVolumeInfo.getPoolId();
+            StoragePoolVO destStoragePoolVO = _storagePoolDao.findById(destStoragePoolId);
+
+            HostVO hostVO;
+
+            if (destStoragePoolVO.getClusterId() != null) {
+                hostVO = getHostInCluster(destStoragePoolVO.getClusterId());
+            }
+            else {
+                hostVO = getHost(destVolumeInfo.getDataCenterId(), hypervisorType, false);
+            }
+
+            setCertainVolumeValuesNull(destVolumeInfo.getId());
+
+            // migrate the volume via the hypervisor
+            String path = migrateVolume(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from managed storage to non-managed storage");
+
+            updateVolumePath(destVolumeInfo.getId(), path);
+        }
+        catch (Exception ex) {
+            errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeMigrationFromManagedStorageToNonManagedStorage': " +
+                    ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            CopyCmdAnswer copyCmdAnswer;
+
+            if (errMsg != null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+            else {
+                destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
+
+                DataTO dataTO = destVolumeInfo.getTO();
+
+                copyCmdAnswer = new CopyCmdAnswer(dataTO);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
 
-        return "Destination data store = " + destData.getDataStore().getName() + ".";
+    private void verifyFormat(ImageFormat imageFormat) {
+        if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA && imageFormat != ImageFormat.QCOW2) {
+            throw new CloudRuntimeException("Only the following image types are currently supported: " +
+                    ImageFormat.VHD.toString() + ", " + ImageFormat.OVA.toString() + ", and " + ImageFormat.QCOW2);
+        }
     }
 
-    private void validate(SnapshotInfo snapshotInfo) {
+    private void verifyFormat(SnapshotInfo snapshotInfo) {
         long volumeId = snapshotInfo.getVolumeId();
 
         VolumeVO volumeVO = _volumeDao.findByIdIncludingRemoved(volumeId);
 
-        if (volumeVO.getFormat() != ImageFormat.VHD) {
-            throw new CloudRuntimeException("Only the " + ImageFormat.VHD.toString() + " image type is currently supported.");
-        }
+        verifyFormat(volumeVO.getFormat());
     }
 
     private boolean usingBackendSnapshotFor(SnapshotInfo snapshotInfo) {
-        String property = getProperty(snapshotInfo.getId(), "takeSnapshot");
+        String property = getSnapshotProperty(snapshotInfo.getId(), "takeSnapshot");
 
         return Boolean.parseBoolean(property);
     }
 
-    protected boolean needCacheStorage(DataObject srcData, DataObject destData) {
+    private boolean needCacheStorage(DataObject srcData, DataObject destData) {
         DataTO srcTO = srcData.getTO();
         DataStoreTO srcStoreTO = srcTO.getDataStore();
         DataTO destTO = destData.getTO();
         DataStoreTO destStoreTO = destTO.getDataStore();
 
-        // both snapshot and volume are on primary datastore. No need for a cache storage as
-        // hypervisor will copy directly
+        // both snapshot and volume are on primary datastore - no need for a cache storage as hypervisor will copy directly
         if (srcStoreTO instanceof PrimaryDataStoreTO && destStoreTO instanceof PrimaryDataStoreTO) {
             return false;
         }
@@ -304,14 +564,15 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             return false;
         }
 
-
         if (destStoreTO instanceof NfsTO || destStoreTO.getRole() == DataStoreRole.ImageCache) {
             return false;
         }
+
         if (LOGGER.isDebugEnabled()) {
-            LOGGER.debug("needCacheStorage true, dest at " + destTO.getPath() + " dest role " + destStoreTO.getRole().toString() + srcTO.getPath() + " src role " +
-                srcStoreTO.getRole().toString());
+            LOGGER.debug("needCacheStorage true; dest at " + destTO.getPath() + ", dest role " + destStoreTO.getRole().toString() + "; src at " +
+                    srcTO.getPath() + ", src role " + srcStoreTO.getRole().toString());
         }
+
         return true;
     }
 
@@ -320,6 +581,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         Scope destScope = destData.getDataStore().getScope();
 
         Scope selectedScope = null;
+
         if (srcScope.getScopeId() != null) {
             selectedScope = getZoneScope(srcScope);
         } else if (destScope.getScopeId() != null) {
@@ -327,86 +589,234 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         } else {
             LOGGER.warn("Cannot find a zone-wide scope for movement that needs a cache storage");
         }
+
         return selectedScope;
     }
 
     private Scope getZoneScope(Scope scope) {
         ZoneScope zoneScope;
+
         if (scope instanceof ClusterScope) {
             ClusterScope clusterScope = (ClusterScope)scope;
+
             zoneScope = new ZoneScope(clusterScope.getZoneId());
         } else if (scope instanceof HostScope) {
             HostScope hostScope = (HostScope)scope;
+
             zoneScope = new ZoneScope(hostScope.getZoneId());
         } else {
             zoneScope = (ZoneScope)scope;
         }
+
         return zoneScope;
     }
 
+    private void handleVolumeMigrationFromNonManagedStorageToManagedStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
+                                                                            AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+
+        try {
+            HypervisorType hypervisorType = srcVolumeInfo.getHypervisorType();
+
+            if (!HypervisorType.KVM.equals(hypervisorType)) {
+                throw new CloudRuntimeException("Currently, only the KVM hypervisor type is supported for the migration of a volume " +
+                        "from non-managed storage to managed storage.");
+            }
+
+            VirtualMachine vm = srcVolumeInfo.getAttachedVM();
+
+            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
+                throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage is attached to " +
+                        "a VM, the VM must be in the Stopped state.");
+            }
+
+            destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
+
+            VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
+
+            volumeVO.setPath(volumeVO.get_iScsiName());
+
+            _volumeDao.update(volumeVO.getId(), volumeVO);
+
+            destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
+
+            long srcStoragePoolId = srcVolumeInfo.getPoolId();
+            StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(srcStoragePoolId);
+
+            HostVO hostVO;
+
+            if (srcStoragePoolVO.getClusterId() != null) {
+                hostVO = getHostInCluster(srcStoragePoolVO.getClusterId());
+            }
+            else {
+                hostVO = getHost(destVolumeInfo.getDataCenterId(), hypervisorType, false);
+            }
+
+            // migrate the volume via the hypervisor
+            migrateVolume(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage");
+
+            volumeVO = _volumeDao.findById(destVolumeInfo.getId());
+
+            volumeVO.setFormat(ImageFormat.QCOW2);
+
+            _volumeDao.update(volumeVO.getId(), volumeVO);
+        }
+        catch (Exception ex) {
+            errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeMigrationFromNonManagedStorageToManagedStorage': " +
+                    ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            CopyCmdAnswer copyCmdAnswer;
+
+            if (errMsg != null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+            else {
+                destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
+
+                DataTO dataTO = destVolumeInfo.getTO();
+
+                copyCmdAnswer = new CopyCmdAnswer(dataTO);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
     /**
-     * This function is responsible for copying a volume from the managed store to a secondary store. This is used in two cases
+     * This function is responsible for copying a snapshot from managed storage to secondary storage. This is used in the following two cases:
      * 1) When creating a template from a snapshot
      * 2) When createSnapshot is called with location=SECONDARY
      *
-     * @param snapshotInfo Source snapshot
+     * @param snapshotInfo source snapshot
      * @param destData destination (can be template or snapshot)
      * @param callback callback for async
      */
     private void handleCopyDataToSecondaryStorage(SnapshotInfo snapshotInfo, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+        CopyCmdAnswer copyCmdAnswer = null;
+        boolean usingBackendSnapshot = false;
+
         try {
             snapshotInfo.processEvent(Event.CopyingRequested);
-        }
-        catch (Exception ex) {
-            throw new CloudRuntimeException("This snapshot is not currently in a state where it can be used to create a template.");
-        }
 
-        HostVO hostVO = getHost(snapshotInfo);
+            HostVO hostVO = getHost(snapshotInfo);
 
-        boolean usingBackendSnapshot = usingBackendSnapshotFor(snapshotInfo);
-        boolean computeClusterSupportsResign = clusterDao.getSupportsResigning(hostVO.getClusterId());
-        boolean needCache = needCacheStorage(snapshotInfo, destData);
+            boolean needCache = needCacheStorage(snapshotInfo, destData);
 
-        DataObject destOnStore = destData;
+            DataObject destOnStore = destData;
 
-        if (needCache) {
-            // creates an object in the DB for data to be cached
-            Scope selectedScope = pickCacheScopeForCopy(snapshotInfo, destData);
-            destOnStore = cacheMgr.getCacheObject(snapshotInfo, selectedScope);
-            destOnStore.processEvent(Event.CreateOnlyRequested);
-        }
+            if (needCache) {
+                // creates an object in the DB for data to be cached
+                Scope selectedScope = pickCacheScopeForCopy(snapshotInfo, destData);
 
-        if (usingBackendSnapshot && !computeClusterSupportsResign) {
-            String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " + hostVO.getClusterId();
+                destOnStore = cacheMgr.getCacheObject(snapshotInfo, selectedScope);
 
-            LOGGER.warn(noSupportForResignErrMsg);
+                destOnStore.processEvent(Event.CreateOnlyRequested);
+            }
 
-            throw new CloudRuntimeException(noSupportForResignErrMsg);
-        }
+            usingBackendSnapshot = usingBackendSnapshotFor(snapshotInfo);
 
-        try {
             if (usingBackendSnapshot) {
-                createVolumeFromSnapshot(hostVO, snapshotInfo, true);
+                final boolean computeClusterSupportsVolumeClone;
+
+                // only XenServer, VMware, and KVM are currently supported
+                if (HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
+                    computeClusterSupportsVolumeClone = clusterDao.getSupportsResigning(hostVO.getClusterId());
+                }
+                else if (HypervisorType.VMware.equals(snapshotInfo.getHypervisorType()) || HypervisorType.KVM.equals(snapshotInfo.getHypervisorType())) {
+                    computeClusterSupportsVolumeClone = true;
+                }
+                else {
+                    throw new CloudRuntimeException("Unsupported hypervisor type");
+                }
+
+                if (!computeClusterSupportsVolumeClone) {
+                    String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " +
+                            hostVO.getClusterId();
+
+                    LOGGER.warn(noSupportForResignErrMsg);
+
+                    throw new CloudRuntimeException(noSupportForResignErrMsg);
+                }
             }
 
+            String vmdk = null;
+            String uuid = null;
+            boolean keepGrantedAccess = false;
+
             DataStore srcDataStore = snapshotInfo.getDataStore();
 
+            if (usingBackendSnapshot) {
+                createVolumeFromSnapshot(snapshotInfo);
+
+                if (HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType()) || HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                    keepGrantedAccess = HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType());
+
+                    Map<String, String> extraDetails = null;
+
+                    if (HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                        extraDetails = new HashMap<>();
+
+                        String extraDetailsVmdk = getSnapshotProperty(snapshotInfo.getId(), DiskTO.VMDK);
+
+                        extraDetails.put(DiskTO.VMDK, extraDetailsVmdk);
+                        extraDetails.put(DiskTO.TEMPLATE_RESIGN, Boolean.TRUE.toString());
+                    }
+
+                    copyCmdAnswer = performResignature(snapshotInfo, hostVO, extraDetails, keepGrantedAccess);
+
+                    // If using VMware, have the host rescan its software HBA if dynamic discovery is in use.
+                    if (HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                        String iqn = getSnapshotProperty(snapshotInfo.getId(), DiskTO.IQN);
+
+                        disconnectHostFromVolume(hostVO, srcDataStore.getId(), iqn);
+                    }
+
+                    if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
+                        if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
+                            throw new CloudRuntimeException(copyCmdAnswer.getDetails());
+                        } else {
+                            throw new CloudRuntimeException("Unable to create volume from snapshot");
+                        }
+                    }
+
+                    vmdk = copyCmdAnswer.getNewData().getPath();
+                    uuid = UUID.randomUUID().toString();
+                }
+            }
+
             String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
             int primaryStorageDownloadWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
-            CopyCommand copyCommand = new CopyCommand(snapshotInfo.getTO(), destOnStore.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
-
-            String errMsg = null;
-            CopyCmdAnswer copyCmdAnswer = null;
+            CopyCommand copyCommand = new CopyCommand(snapshotInfo.getTO(), destOnStore.getTO(), primaryStorageDownloadWait,
+                    VirtualMachineManager.ExecuteInSequence.value());
 
             try {
-                // If we are using a back-end snapshot, then we should still have access to it from the hosts in the cluster that hostVO is in
-                // (because we passed in true as the third parameter to createVolumeFromSnapshot above).
-                if (!usingBackendSnapshot) {
+                if (!keepGrantedAccess) {
                     _volumeService.grantAccess(snapshotInfo, hostVO, srcDataStore);
                 }
 
                 Map<String, String> srcDetails = getSnapshotDetails(snapshotInfo);
 
+                if (isForVMware(destData)) {
+                    srcDetails.put(DiskTO.VMDK, vmdk);
+                    srcDetails.put(DiskTO.UUID, uuid);
+
+                    if (destData instanceof TemplateInfo) {
+                        VMTemplateVO templateDataStoreVO = _vmTemplateDao.findById(destData.getId());
+
+                        templateDataStoreVO.setUniqueName(uuid);
+
+                        _vmTemplateDao.update(destData.getId(), templateDataStoreVO);
+                    }
+                }
+
                 copyCommand.setOptions(srcDetails);
 
                 copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
@@ -423,15 +833,16 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
                     // storage), at this point, the data has been copied from the primary
                     // to the NFS cache by the hypervisor. We now invoke another copy
                     // command to copy this data from cache to secondary storage. We
-                    // then cleanup the cache
+                    // then clean up the cache.
 
                     destOnStore.processEvent(Event.OperationSuccessed, copyCmdAnswer);
 
-                    CopyCommand cmd = new CopyCommand(destOnStore.getTO(), destData.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
+                    CopyCommand cmd = new CopyCommand(destOnStore.getTO(), destData.getTO(), primaryStorageDownloadWait,
+                            VirtualMachineManager.ExecuteInSequence.value());
                     EndPoint ep = selector.select(destOnStore, destData);
 
                     if (ep == null) {
-                        errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
+                        errMsg = "No remote endpoint to send command, check if host or SSVM is down";
 
                         LOGGER.error(errMsg);
 
@@ -443,16 +854,22 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
                     // clean up snapshot copied to staging
                     cacheMgr.deleteCacheObject(destOnStore);
                 }
-
             } catch (CloudRuntimeException | AgentUnavailableException | OperationTimedoutException ex) {
                 String msg = "Failed to create template from snapshot (Snapshot ID = " + snapshotInfo.getId() + ") : ";
 
                 LOGGER.warn(msg, ex);
 
-                throw new CloudRuntimeException(msg + ex.getMessage());
+                throw new CloudRuntimeException(msg + ex.getMessage(), ex);
             } finally {
                 _volumeService.revokeAccess(snapshotInfo, hostVO, srcDataStore);
 
+                // If using VMware, have the host rescan its software HBA if dynamic discovery is in use.
+                if (HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                    String iqn = getSnapshotProperty(snapshotInfo.getId(), DiskTO.IQN);
+
+                    disconnectHostFromVolume(hostVO, srcDataStore.getId(), iqn);
+                }
+
                 if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
                     if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
                         errMsg = copyCmdAnswer.getDetails();
@@ -478,111 +895,156 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
                     LOGGER.warn("Error processing snapshot event: " + ex.getMessage(), ex);
                 }
             }
+        }
+        catch (Exception ex) {
+            errMsg = ex.getMessage();
 
-            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
-
-            result.setResult(errMsg);
-
-            callback.complete(result);
+            throw new CloudRuntimeException(errMsg);
         }
         finally {
             if (usingBackendSnapshot) {
                 deleteVolumeFromSnapshot(snapshotInfo);
             }
-        }
+
+            if (copyCmdAnswer == null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
     }
 
     /**
      * Creates a volume on the storage from a snapshot that resides on the secondary storage (archived snapshot).
      * @param snapshotInfo snapshot on secondary
      * @param volumeInfo volume to be created on the storage
-     * @param callback  for async
+     * @param callback for async
      */
-    private void handleCreateVolumeFromSnapshotOnSecondaryStorage(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
-        // at this point, the snapshotInfo and volumeInfo should have the same disk offering ID (so either one should be OK to get a DiskOfferingVO instance)
-        DiskOfferingVO diskOffering = _diskOfferingDao.findByIdIncludingRemoved(volumeInfo.getDiskOfferingId());
-        SnapshotVO snapshot = _snapshotDao.findById(snapshotInfo.getId());
+    private void handleCreateVolumeFromSnapshotOnSecondaryStorage(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
+                                                                  AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+        CopyCmdAnswer copyCmdAnswer = null;
 
-        // update the volume's hv_ss_reserve (hypervisor snapshot reserve) from a disk offering (used for managed storage)
-        _volumeService.updateHypervisorSnapshotReserveForVolume(diskOffering, volumeInfo.getId(), snapshot.getHypervisorType());
+        try {
+            // at this point, the snapshotInfo and volumeInfo should have the same disk offering ID (so either one should be OK to get a DiskOfferingVO instance)
+            DiskOfferingVO diskOffering = _diskOfferingDao.findByIdIncludingRemoved(volumeInfo.getDiskOfferingId());
+            SnapshotVO snapshot = _snapshotDao.findById(snapshotInfo.getId());
 
-        CopyCmdAnswer copyCmdAnswer = null;
-        String errMsg = null;
+            // update the volume's hv_ss_reserve (hypervisor snapshot reserve) from a disk offering (used for managed storage)
+            _volumeService.updateHypervisorSnapshotReserveForVolume(diskOffering, volumeInfo.getId(), snapshot.getHypervisorType());
 
-        HostVO hostVO = null;
+            HostVO hostVO;
 
-        try {
             // create a volume on the storage
             AsyncCallFuture<VolumeApiResult> future = _volumeService.createVolumeAsync(volumeInfo, volumeInfo.getDataStore());
             VolumeApiResult result = future.get();
 
             if (result.isFailed()) {
                 LOGGER.error("Failed to create a volume: " + result.getResult());
+
                 throw new CloudRuntimeException(result.getResult());
             }
 
             volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
-
             volumeInfo.processEvent(Event.MigrationRequested);
-
             volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
 
-            hostVO = getHost(snapshotInfo.getDataCenterId(), false);
+            hostVO = getHost(snapshotInfo.getDataCenterId(), snapshotInfo.getHypervisorType(), false);
 
             // copy the volume from secondary via the hypervisor
-            copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, hostVO);
+            if (HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
+                copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, hostVO);
+            }
+            else {
+                copyCmdAnswer = copyImageToVolume(snapshotInfo, volumeInfo, hostVO);
+            }
 
             if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
                 if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
-                    errMsg = copyCmdAnswer.getDetails();
+                    throw new CloudRuntimeException(copyCmdAnswer.getDetails());
                 }
                 else {
-                    errMsg = "Unable to create volume from snapshot";
+                    throw new CloudRuntimeException("Unable to create volume from snapshot");
                 }
             }
         }
         catch (Exception ex) {
-            errMsg = ex.getMessage() != null ? ex.getMessage() : "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotBothOnStorageSystem'";
+            errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotOnSecondaryStorage': " +
+                    ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
         }
+        finally {
+            if (copyCmdAnswer == null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
 
-        CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
 
-        result.setResult(errMsg);
+            result.setResult(errMsg);
 
-        callback.complete(result);
+            callback.complete(result);
+        }
     }
 
     /**
      * Clones a template present on the storage to a new volume and resignatures it.
      *
-     * @param templateInfo   source template
-     * @param volumeInfo  destination ROOT volume
-     * @param callback  for async
+     * @param templateInfo source template
+     * @param volumeInfo destination ROOT volume
+     * @param callback for async
      */
     private void handleCreateVolumeFromTemplateBothOnStorageSystem(TemplateInfo templateInfo, VolumeInfo volumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
-        Preconditions.checkArgument(templateInfo != null, "Passing 'null' to templateInfo of handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
-        Preconditions.checkArgument(volumeInfo != null, "Passing 'null' to volumeInfo of handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
-
-        CopyCmdAnswer copyCmdAnswer = null;
         String errMsg = null;
+        CopyCmdAnswer copyCmdAnswer = null;
 
-        HostVO hostVO = getHost(volumeInfo.getDataCenterId(), true);
+        try {
+            Preconditions.checkArgument(templateInfo != null, "Passing 'null' to templateInfo of " +
+                            "handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
+            Preconditions.checkArgument(volumeInfo != null, "Passing 'null' to volumeInfo of " +
+                            "handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
 
-        if (hostVO == null) {
-            throw new CloudRuntimeException("Unable to locate a host capable of resigning in the zone with the following ID: " + volumeInfo.getDataCenterId());
-        }
+            verifyFormat(templateInfo.getFormat());
 
-        boolean computeClusterSupportsResign = clusterDao.getSupportsResigning(hostVO.getClusterId());
+            HostVO hostVO = null;
 
-        if (!computeClusterSupportsResign) {
-            String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " + hostVO.getClusterId();
+            final boolean computeClusterSupportsVolumeClone;
 
-            LOGGER.warn(noSupportForResignErrMsg);
+            // only XenServer, VMware, and KVM are currently supported
+            // Leave host equal to null for KVM since we don't need to perform a resignature when using that hypervisor type.
+            if (volumeInfo.getFormat() == ImageFormat.VHD) {
+                hostVO = getHost(volumeInfo.getDataCenterId(), HypervisorType.XenServer, true);
 
-            throw new CloudRuntimeException(noSupportForResignErrMsg);
-        }
+                if (hostVO == null) {
+                    throw new CloudRuntimeException("Unable to locate a host capable of resigning in the zone with the following ID: " +
+                            volumeInfo.getDataCenterId());
+                }
+
+                computeClusterSupportsVolumeClone = clusterDao.getSupportsResigning(hostVO.getClusterId());
+
+                if (!computeClusterSupportsVolumeClone) {
+                    String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " +
+                            hostVO.getClusterId();
+
+                    LOGGER.warn(noSupportForResignErrMsg);
+
+                    throw new CloudRuntimeException(noSupportForResignErrMsg);
+                }
+            }
+            else if (volumeInfo.getFormat() == ImageFormat.OVA) {
+                // all VMware hosts support resigning
+                hostVO = getHost(volumeInfo.getDataCenterId(), HypervisorType.VMware, false);
+
+                if (hostVO == null) {
+                    throw new CloudRuntimeException("Unable to locate a host capable of resigning in the zone with the following ID: " +
+                            volumeInfo.getDataCenterId());
+                }
+            }
 
-        try {
             VolumeDetailVO volumeDetail = new VolumeDetailVO(volumeInfo.getId(),
                     "cloneOfTemplate",
                     String.valueOf(templateInfo.getId()),
@@ -591,6 +1053,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             volumeDetail = volumeDetailsDao.persist(volumeDetail);
 
             AsyncCallFuture<VolumeApiResult> future = _volumeService.createVolumeAsync(volumeInfo, volumeInfo.getDataStore());
+
             int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(_configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
 
             VolumeApiResult result = future.get(storagePoolMaxWaitSeconds, TimeUnit.SECONDS);
@@ -601,6 +1064,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 
             if (result.isFailed()) {
                 LOGGER.warn("Failed to create a volume: " + result.getResult());
+
                 throw new CloudRuntimeException(result.getResult());
             }
 
@@ -608,48 +1072,100 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             volumeInfo.processEvent(Event.MigrationRequested);
             volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
 
-            copyCmdAnswer = performResignature(volumeInfo, hostVO);
+            if (hostVO != null) {
+                Map<String, String> extraDetails = null;
 
-            if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
-                if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
-                    throw new CloudRuntimeException(copyCmdAnswer.getDetails());
-                } else {
-                    throw new CloudRuntimeException("Unable to create a volume from a template");
+                if (HypervisorType.VMware.equals(templateInfo.getHypervisorType())) {
+                    extraDetails = new HashMap<>();
+
+                    String extraDetailsVmdk = templateInfo.getUniqueName() + ".vmdk";
+
+                    extraDetails.put(DiskTO.VMDK, extraDetailsVmdk);
+                    extraDetails.put(DiskTO.EXPAND_DATASTORE, Boolean.TRUE.toString());
                 }
+
+                copyCmdAnswer = performResignature(volumeInfo, hostVO, extraDetails);
+
+                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
+                    if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
+                        throw new CloudRuntimeException(copyCmdAnswer.getDetails());
+                    } else {
+                        throw new CloudRuntimeException("Unable to create a volume from a template");
+                    }
+                }
+
+                // If using VMware, have the host rescan its software HBA if dynamic discovery is in use.
+                if (HypervisorType.VMware.equals(templateInfo.getHypervisorType())) {
+                    disconnectHostFromVolume(hostVO, volumeInfo.getPoolId(), volumeInfo.get_iScsiName());
+                }
+            }
+            else {
+                VolumeObjectTO newVolume = new VolumeObjectTO();
+
+                newVolume.setSize(volumeInfo.getSize());
+                newVolume.setPath(volumeInfo.getPath());
+                newVolume.setFormat(volumeInfo.getFormat());
+
+                copyCmdAnswer = new CopyCmdAnswer(newVolume);
+            }
+        } catch (Exception ex) {
+            try {
+                volumeInfo.getDataStore().getDriver().deleteAsync(volumeInfo.getDataStore(), volumeInfo, null);
+            }
+            catch (Exception exc) {
+                LOGGER.warn("Failed to delete volume", exc);
+            }
+
+            if (templateInfo != null) {
+                errMsg = "Create volume from template (ID = " + templateInfo.getId() + ") failed: " + ex.getMessage();
+            }
+            else {
+                errMsg = "Create volume from template failed: " + ex.getMessage();
             }
-        } catch (InterruptedException | ExecutionException | TimeoutException ex ) {
-            volumeInfo.getDataStore().getDriver().deleteAsync(volumeInfo.getDataStore(), volumeInfo, null);
 
-            throw new CloudRuntimeException("Create volume from template (ID = " + templateInfo.getId() + ") failed " + ex.getMessage());
+            throw new CloudRuntimeException(errMsg);
         }
+        finally {
+            if (copyCmdAnswer == null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
 
-        CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
 
-        result.setResult(errMsg);
+            result.setResult(errMsg);
 
-        callback.complete(result);
+            callback.complete(result);
+        }
     }
 
-    private void handleCreateVolumeFromSnapshotBothOnStorageSystem(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
-        CopyCmdAnswer copyCmdAnswer = null;
+    private void handleCreateVolumeFromSnapshotBothOnStorageSystem(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
+                                                                   AsyncCompletionCallback<CopyCommandResult> callback) {
         String errMsg = null;
+        CopyCmdAnswer copyCmdAnswer = null;
 
         try {
+            verifyFormat(snapshotInfo);
+
             HostVO hostVO = getHost(snapshotInfo);
 
             boolean usingBackendSnapshot = usingBackendSnapshotFor(snapshotInfo);
-            boolean computeClusterSupportsResign = clusterDao.getSupportsResigning(hostVO.getClusterId());
+            boolean computeClusterSupportsVolumeClone = true;
+
+            if (HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
+                computeClusterSupportsVolumeClone = clusterDao.getSupportsResigning(hostVO.getClusterId());
 
-            if (usingBackendSnapshot && !computeClusterSupportsResign) {
-                String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " + hostVO.getClusterId();
+                if (usingBackendSnapshot && !computeClusterSupportsVolumeClone) {
+                    String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " +
+                            hostVO.getClusterId();
 
-                LOGGER.warn(noSupportForResignErrMsg);
+                    LOGGER.warn(noSupportForResignErrMsg);
 
-                throw new CloudRuntimeException(noSupportForResignErrMsg);
+                    throw new CloudRuntimeException(noSupportForResignErrMsg);
+                }
             }
 
             boolean canStorageSystemCreateVolumeFromVolume = canStorageSystemCreateVolumeFromVolume(snapshotInfo);
-            boolean useCloning = usingBackendSnapshot || (canStorageSystemCreateVolumeFromVolume && computeClusterSupportsResign);
+            boolean useCloning = usingBackendSnapshot || (canStorageSystemCreateVolumeFromVolume && computeClusterSupportsVolumeClone);
 
             VolumeDetailVO volumeDetail = null;
 
@@ -670,7 +1186,6 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             _volumeService.updateHypervisorSnapshotReserveForVolume(diskOffering, volumeInfo.getId(), snapshot.getHypervisorType());
 
             AsyncCallFuture<VolumeApiResult> future = _volumeService.createVolumeAsync(volumeInfo, volumeInfo.getDataStore());
-
             VolumeApiResult result = future.get();
 
             if (volumeDetail != null) {
@@ -680,99 +1195,523 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             if (result.isFailed()) {
                 LOGGER.warn("Failed to create a volume: " + result.getResult());
 
-                throw new CloudRuntimeException(result.getResult());
-            }
+                throw new CloudRuntimeException(result.getResult());
+            }
+
+            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
+            volumeInfo.processEvent(Event.MigrationRequested);
+            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
+
+            if (HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType()) || HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                if (useCloning) {
+                    Map<String, String> extraDetails = null;
+
+                    if (HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                        extraDetails = new HashMap<>();
+
+                        String extraDetailsVmdk = getSnapshotProperty(snapshotInfo.getId(), DiskTO.VMDK);
+
+                        extraDetails.put(DiskTO.VMDK, extraDetailsVmdk);
+                    }
+
+                    copyCmdAnswer = performResignature(volumeInfo, hostVO, extraDetails);
+
+                    // If using VMware, have the host rescan its software HBA if dynamic discovery is in use.
+                    if (HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
+                        disconnectHostFromVolume(hostVO, volumeInfo.getPoolId(), volumeInfo.get_iScsiName());
+                    }
+                } else {
+                    // asking for a XenServer host here so we don't always prefer to use XenServer hosts that support resigning
+                    // even when we don't need those hosts to do this kind of copy work
+                    hostVO = getHost(snapshotInfo.getDataCenterId(), snapshotInfo.getHypervisorType(), false);
+
+                    copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, hostVO);
+                }
+
+                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
+                    if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
+                        throw new CloudRuntimeException(copyCmdAnswer.getDetails());
+                    } else {
+                        throw new CloudRuntimeException("Unable to create volume from snapshot");
+                    }
+                }
+            }
+            else if (HypervisorType.KVM.equals(snapshotInfo.getHypervisorType())) {
+                VolumeObjectTO newVolume = new VolumeObjectTO();
+
+                newVolume.setSize(volumeInfo.getSize());
+                newVolume.setPath(volumeInfo.get_iScsiName());
+                newVolume.setFormat(volumeInfo.getFormat());
+
+                copyCmdAnswer = new CopyCmdAnswer(newVolume);
+            }
+            else {
+                throw new CloudRuntimeException("Unsupported hypervisor type");
+            }
+        }
+        catch (Exception ex) {
+            errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotBothOnStorageSystem': " +
+                    ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            if (copyCmdAnswer == null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void handleCreateVolumeFromVolumeOnSecondaryStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
+                                                                long dataCenterId, HypervisorType hypervisorType,
+                                                                AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+        CopyCmdAnswer copyCmdAnswer = null;
+
+        try {
+            // create a volume on the storage
+            destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
+
+            destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
+
+            HostVO hostVO = getHost(dataCenterId, hypervisorType, false);
+
+            // copy the volume from secondary via the hypervisor
+            copyCmdAnswer = copyImageToVolume(srcVolumeInfo, destVolumeInfo, hostVO);
+
+            if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
+                if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
+                    throw new CloudRuntimeException(copyCmdAnswer.getDetails());
+                }
+                else {
+                    throw new CloudRuntimeException("Unable to create volume from volume");
+                }
+            }
+        }
+        catch (Exception ex) {
+            errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromVolumeOnSecondaryStorage': " +
+                    ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            if (copyCmdAnswer == null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private CopyCmdAnswer copyImageToVolume(DataObject srcDataObject, VolumeInfo destVolumeInfo, HostVO hostVO) {
+        String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
+        int primaryStorageDownloadWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
+
+        CopyCommand copyCommand = new CopyCommand(srcDataObject.getTO(), destVolumeInfo.getTO(), primaryStorageDownloadWait,
+                VirtualMachineManager.ExecuteInSequence.value());
+
+        CopyCmdAnswer copyCmdAnswer;
+
+        try {
+            _volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
+
+            Map<String, String> destDetails = getVolumeDetails(destVolumeInfo);
+
+            copyCommand.setOptions2(destDetails);
+
+            copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
+        }
+        catch (CloudRuntimeException | AgentUnavailableException | OperationTimedoutException ex) {
+            String msg = "Failed to copy image : ";
+
+            LOGGER.warn(msg, ex);
+
+            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
+        }
+        finally {
+            _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
+        }
+
+        VolumeObjectTO volumeObjectTO = (VolumeObjectTO)copyCmdAnswer.getNewData();
+
+        volumeObjectTO.setFormat(ImageFormat.QCOW2);
+
+        return copyCmdAnswer;
+    }
+
+    /**
+     * If the underlying storage system is making use of read-only snapshots, this gives the storage system the opportunity to
+     * create a volume from the snapshot so that we can copy the VHD file that should be inside of the snapshot to secondary storage.
+     *
+     * The resultant volume must be writable because we need to resign the SR and the VDI that should be inside of it before we copy
+     * the VHD file to secondary storage.
+     *
+     * If the storage system is using writable snapshots, then nothing need be done by that storage system here because we can just
+     * resign the SR and the VDI that should be inside of the snapshot before copying the VHD file to secondary storage.
+     */
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
+        SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "create");
+
+        try {
+            snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(), snapshotInfo, null);
+        }
+        finally {
+            _snapshotDetailsDao.remove(snapshotDetails.getId());
+        }
+    }
+
+    /**
+     * If the underlying storage system needed to create a volume from a snapshot for createVolumeFromSnapshot(SnapshotInfo), then
+     * this is its opportunity to delete that temporary volume and restore properties in snapshot_details to the way they were before the
+     * invocation of createVolumeFromSnapshot(SnapshotInfo).
+     */
+    private void deleteVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
+        SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "delete");
+
+        try {
+            snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(), snapshotInfo, null);
+        }
+        finally {
+            _snapshotDetailsDao.remove(snapshotDetails.getId());
+        }
+    }
+
+    private SnapshotDetailsVO handleSnapshotDetails(long csSnapshotId, String value) {
+        String name = "tempVolume";
+
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+
+        return _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    /**
+     * For each disk to migrate:
+     *   Create a volume on the target storage system.
+     *   Make the newly created volume accessible to the target KVM host.
+     *   Send a command to the target KVM host to connect to the newly created volume.
+     * Send a command to the source KVM host to migrate the VM and its storage.
+     */
+    @Override
+    public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
+        String errMsg = null;
+
+        try {
+            if (srcHost.getHypervisorType() != HypervisorType.KVM) {
+                throw new CloudRuntimeException("Invalid hypervisor type (only KVM supported for this operation at the time being)");
+            }
+
+            verifyLiveMigrationMapForKVM(volumeDataStoreMap);
+
+            Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
+            Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
+
+            for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
+                VolumeInfo srcVolumeInfo = entry.getKey();
+                DataStore destDataStore = entry.getValue();
+
+                VolumeVO srcVolume = _volumeDao.findById(srcVolumeInfo.getId());
+                StoragePoolVO destStoragePool = _storagePoolDao.findById(destDataStore.getId());
+
+                VolumeVO destVolume = duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
+                VolumeInfo destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
+
+                // move the volume from Allocated to Creating
+                destVolumeInfo.processEvent(Event.MigrationCopyRequested);
+                // move the volume from Creating to Ready
+                destVolumeInfo.processEvent(Event.MigrationCopySucceeded);
+                // move the volume from Ready to Migrating
+                destVolumeInfo.processEvent(Event.MigrationRequested);
+
+                // create a volume on the destination storage
+                destDataStore.getDriver().createAsync(destDataStore, destVolumeInfo, null);
+
+                destVolume = _volumeDao.findById(destVolume.getId());
+
+                destVolume.setPath(destVolume.get_iScsiName());
+
+                _volumeDao.update(destVolume.getId(), destVolume);
+
+                destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
+
+                _volumeService.grantAccess(destVolumeInfo, destHost, destDataStore);
+
+                String connectedPath = connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
+
+                MigrateCommand.MigrateDiskInfo migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
+                        MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
+                        MigrateCommand.MigrateDiskInfo.DriverType.RAW,
+                        MigrateCommand.MigrateDiskInfo.Source.DEV,
+                        connectedPath);
+
+                migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
+
+                srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo);
+            }
+
+            PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
+
+            try {
+                Answer pfma = _agentMgr.send(destHost.getId(), pfmc);
+
+                if (pfma == null || !pfma.getResult()) {
+                    String details = pfma != null ? pfma.getDetails() : "null answer returned";
+                    String msg = "Unable to prepare for migration due to the following: " + details;
+
+                    throw new AgentUnavailableException(msg, destHost.getId());
+                }
+            }
+            catch (final OperationTimedoutException e) {
+                throw new AgentUnavailableException("Operation timed out", destHost.getId());
+            }
+
+            VMInstanceVO vm = _vmDao.findById(vmTO.getId());
+            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);
+
+            String autoConvergence = _configDao.getValue(Config.KvmAutoConvergence.toString());
+            boolean kvmAutoConvergence = Boolean.parseBoolean(autoConvergence);
+
+            migrateCommand.setAutoConvergence(kvmAutoConvergence);
+
+            MigrateAnswer migrateAnswer = (MigrateAnswer)_agentMgr.send(srcHost.getId(), migrateCommand);
+
+            boolean success = migrateAnswer != null && migrateAnswer.getResult();
+
+            handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost);
+
+            if (migrateAnswer == null) {
+                throw new CloudRuntimeException("Unable to get an answer to the migrate command");
+            }
+
+            if (!migrateAnswer.getResult()) {
+                errMsg = migrateAnswer.getDetails();
+
+                throw new CloudRuntimeException(errMsg);
+            }
+        }
+        catch (Exception ex) {
+            errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.copyAsync': " + ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void handlePostMigration(boolean success, Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host destHost) {
+        if (!success) {
+            try {
+                PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
+
+                pfmc.setRollback(true);
+
+                Answer pfma = _agentMgr.send(destHost.getId(), pfmc);
+
+                if (pfma == null || !pfma.getResult()) {
+                    String details = pfma != null ? pfma.getDetails() : "null answer returned";
+                    String msg = "Unable to rollback prepare for migration due to the following: " + details;
+
+                    throw new AgentUnavailableException(msg, destHost.getId());
+                }
+            }
+            catch (Exception e) {
+                LOGGER.debug("Failed to disconnect one or more (original) dest volumes", e);
+            }
+        }
+
+        for (Map.Entry<VolumeInfo, VolumeInfo> entry : srcVolumeInfoToDestVolumeInfo.entrySet()) {
+            VolumeInfo srcVolumeInfo = entry.getKey();
+            VolumeInfo destVolumeInfo = entry.getValue();
+
+            if (success) {
+                srcVolumeInfo.processEvent(Event.OperationSuccessed);
+                destVolumeInfo.processEvent(Event.OperationSuccessed);
+
+                _volumeDao.updateUuid(srcVolumeInfo.getId(), destVolumeInfo.getId());
+
+                VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
+
+                volumeVO.setFormat(ImageFormat.QCOW2);
+
+                _volumeDao.update(volumeVO.getId(), volumeVO);
+
+                try {
+                    _volumeService.destroyVolume(srcVolumeInfo.getId());
+
+                    srcVolumeInfo = _volumeDataFactory.getVolume(srcVolumeInfo.getId());
+
+                    AsyncCallFuture<VolumeApiResult> destroyFuture = _volumeService.expungeVolumeAsync(srcVolumeInfo);
+
+                    if (destroyFuture.get().isFailed()) {
+                        LOGGER.debug("Failed to clean up source volume on storage");
+                    }
+                } catch (Exception e) {
+                    LOGGER.debug("Failed to clean up source volume on storage", e);
+                }
+
+                // Update the volume ID for snapshots on secondary storage
+                if (!_snapshotDao.listByVolumeId(srcVolumeInfo.getId()).isEmpty()) {
+                    _snapshotDao.updateVolumeIds(srcVolumeInfo.getId(), destVolumeInfo.getId());
+                    _snapshotDataStoreDao.updateVolumeIds(srcVolumeInfo.getId(), destVolumeInfo.getId());
+                }
+            }
+            else {
+                try {
+                    disconnectHostFromVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
+                }
+                catch (Exception e) {
+                    LOGGER.debug("Failed to disconnect (new) dest volume", e);
+                }
+
+                try {
+                    _volumeService.revokeAccess(destVolumeInfo, destHost, destVolumeInfo.getDataStore());
+                }
+                catch (Exception e) {
+                    LOGGER.debug("Failed to revoke access from dest volume", e);
+                }
+
+                destVolumeInfo.processEvent(Event.OperationFailed);
+                srcVolumeInfo.processEvent(Event.OperationFailed);
+
+                try {
+                    _volumeService.destroyVolume(destVolumeInfo.getId());
+
+                    destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId());
+
+                    AsyncCallFuture<VolumeApiResult> destroyFuture = _volumeService.expungeVolumeAsync(destVolumeInfo);
+
+                    if (destroyFuture.get().isFailed()) {
+                        LOGGER.debug("Failed to clean up dest volume on storage");
+                    }
+                } catch (Exception e) {
+                    LOGGER.debug("Failed to clean up dest volume on storage", e);
+                }
+            }
+        }
+    }
+
+    private VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePoolVO storagePoolVO) {
+        Long lastPoolId = volume.getPoolId();
+
+        VolumeVO newVol = new VolumeVO(volume);
+
+        newVol.setInstanceId(null);
+        newVol.setChainInfo(null);
+        newVol.setPath(null);
+        newVol.setFolder(null);
+        newVol.setPodId(storagePoolVO.getPodId());
+        newVol.setPoolId(storagePoolVO.getId());
+        newVol.setLastPoolId(lastPoolId);
+
+        return _volumeDao.persist(newVol);
+    }
+
+    private String connectHostToVolume(Host host, long storagePoolId, String iqn) {
+        ModifyTargetsCommand modifyTargetsCommand = getModifyTargetsCommand(storagePoolId, iqn, true);
+
+        return sendModifyTargetsCommand(modifyTargetsCommand, host.getId()).get(0);
+    }
+
+    private void disconnectHostFromVolume(Host host, long storagePoolId, String iqn) {
+        ModifyTargetsCommand modifyTargetsCommand = getModifyTargetsCommand(storagePoolId, iqn, false);
 
-            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
+        sendModifyTargetsCommand(modifyTargetsCommand, host.getId());
+    }
 
-            volumeInfo.processEvent(Event.MigrationRequested);
+    private ModifyTargetsCommand getModifyTargetsCommand(long storagePoolId, String iqn, boolean add) {
+        StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
 
-            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
+        Map<String, String> details = new HashMap<>();
 
-            if (useCloning) {
-                copyCmdAnswer = performResignature(volumeInfo, hostVO);
-            }
-            else {
-                // asking for a XenServer host here so we don't always prefer to use XenServer hosts that support resigning
-                // even when we don't need those hosts to do this kind of copy work
-                hostVO = getHost(snapshotInfo.getDataCenterId(), false);
+        details.put(ModifyTargetsCommand.IQN, iqn);
+        details.put(ModifyTargetsCommand.STORAGE_TYPE, storagePool.getPoolType().name());
+        details.put(ModifyTargetsCommand.STORAGE_UUID, storagePool.getUuid());
+        details.put(ModifyTargetsCommand.STORAGE_HOST, storagePool.getHostAddress());
+        details.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePool.getPort()));
 
-                copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, hostVO);
-            }
+        ModifyTargetsCommand modifyTargetsCommand = new ModifyTargetsCommand();
 
-            if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
-                if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
-                    errMsg = copyCmdAnswer.getDetails();
-                }
-                else {
-                    errMsg = "Unable to create volume from snapshot";
-                }
-            }
-        }
-        catch (Exception ex) {
-            errMsg = ex.getMessage() != null ? ex.getMessage() : "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotBothOnStorageSystem'";
-        }
+        List<Map<String, String>> targets = new ArrayList<>();
 
-        CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+        targets.add(details);
 
-        result.setResult(errMsg);
+        modifyTargetsCommand.setTargets(targets);
+        modifyTargetsCommand.setApplyToAllHostsInCluster(true);
+        modifyTargetsCommand.setAdd(add);
+        modifyTargetsCommand.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
 
-        callback.complete(result);
+        return modifyTargetsCommand;
     }
 
-    /**
-     * If the underlying storage system is making use of read-only snapshots, this gives the storage system the opportunity to
-     * create a volume from the snapshot so that we can copy the VHD file that should be inside of the snapshot to secondary storage.
-     *
-     * The resultant volume must be writable because we need to resign the SR and the VDI that should be inside of it before we copy
-     * the VHD file to secondary storage.
-     *
-     * If the storage system is using writable snapshots, then nothing need be done by that storage system here because we can just
-     * resign the SR and the VDI that should be inside of the snapshot before copying the VHD file to secondary storage.
-     */
-    private void createVolumeFromSnapshot(HostVO hostVO, SnapshotInfo snapshotInfo, boolean keepGrantedAccess) {
-        SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "tempVolume", "create");
+    private List<String> sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
+        ModifyTargetsAnswer modifyTargetsAnswer = (ModifyTargetsAnswer)_agentMgr.easySend(hostId, cmd);
 
-        try {
-            snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(), snapshotInfo, null);
-        }
-        finally {
-            _snapshotDetailsDao.remove(snapshotDetails.getId());
+        if (modifyTargetsAnswer == null) {
+            throw new CloudRuntimeException("Unable to get an answer to the modify targets command");
         }
 
-        CopyCmdAnswer copyCmdAnswer = performResignature(snapshotInfo, hostVO, keepGrantedAccess);
+        if (!modifyTargetsAnswer.getResult()) {
+            String msg = "Unable to modify targets on the following host: " + hostId;
 
-        if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
-            if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
-                throw new CloudRuntimeException(copyCmdAnswer.getDetails());
-            } else {
-                throw new CloudRuntimeException("Unable to create volume from snapshot");
-            }
+            throw new CloudRuntimeException(msg);
         }
+
+        return modifyTargetsAnswer.getConnectedPaths();
     }
 
-    /**
-     * If the underlying storage system needed to create a volume from a snapshot for createVolumeFromSnapshot(HostVO, SnapshotInfo), then
-     * this is its opportunity to delete that temporary volume and restore properties in snapshot_details to the way they were before the
-     * invocation of createVolumeFromSnapshot(HostVO, SnapshotInfo).
-     */
-    private void deleteVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
-        SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "tempVolume", "delete");
+    /*
+    * At a high level: The source storage cannot be managed and the destination storage must be managed.
+    */
+    private void verifyLiveMigrationMapForKVM(Map<VolumeInfo, DataStore> volumeDataStoreMap) {
+        for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
+            VolumeInfo volumeInfo = entry.getKey();
 
-        try {
-            snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(), snapshotInfo, null);
-        }
-        finally {
-            _snapshotDetailsDao.remove(snapshotDetails.getId());
-        }
-    }
+            Long storagePoolId = volumeInfo.getPoolId();
+            StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(storagePoolId);
 
-    private SnapshotDetailsVO handleSnapshotDetails(long csSnapshotId, String name, String value) {
-        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+            if (srcStoragePoolVO == null) {
+                throw new CloudRuntimeException("Volume with ID " + volumeInfo.getId() + " is not associated with a storage pool.");
+            }
 
-        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+            if (srcStoragePoolVO.isManaged()) {
+                throw new CloudRuntimeException("Migrating a volume online with KVM from managed storage is not currently supported.");
+            }
 
-        return _snapshotDetailsDao.persist(snapshotDetails);
+            DataStore dataStore = entry.getValue();
+            StoragePoolVO destStoragePoolVO = _storagePoolDao.findById(dataStore.getId());
+
+            if (destStoragePoolVO == null) {
+                throw new CloudRuntimeException("Destination storage pool with ID " + dataStore.getId() + " was not located.");
+            }
+
+            if (!destStoragePoolVO.isManaged()) {
+                throw new CloudRuntimeException("Migrating a volume online with KVM can currently only be done when moving to managed storage.");
+            }
+        }
     }
 
     private boolean canStorageSystemCreateVolumeFromVolume(SnapshotInfo snapshotInfo) {
@@ -791,7 +1730,17 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         return supportsCloningVolumeFromVolume;
     }
 
-    private String getProperty(long snapshotId, String property) {
+    private String getVolumeProperty(long volumeId, String property) {
+        VolumeDetailVO volumeDetails = volumeDetailsDao.findDetail(volumeId, property);
+
+        if (volumeDetails != null) {
+            return volumeDetails.getValue();
+        }
+
+        return null;
+    }
+
+    private String getSnapshotProperty(long snapshotId, String property) {
         SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, property);
 
         if (snapshotDetails != null) {
@@ -801,18 +1750,124 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         return null;
     }
 
-    private Map<String, String> getVolumeDetails(VolumeInfo volumeInfo) {
-        Map<String, String> volumeDetails = new HashMap<String, String>();
+    private void handleCreateTemplateFromVolume(VolumeInfo volumeInfo, TemplateInfo templateInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
+        boolean srcVolumeDetached = volumeInfo.getAttachedVM() == null;
 
-        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        String errMsg = null;
+        CopyCmdAnswer copyCmdAnswer = null;
+
+        try {
+            if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat())) {
+                throw new CloudRuntimeException("When using managed storage, you can only create a template from a volume on KVM currently.");
+            }
+
+            volumeInfo.processEvent(Event.MigrationRequested);
+
+            HostVO hostVO = getHost(volumeInfo.getDataCenterId(), HypervisorType.KVM, false);
+            DataStore srcDataStore = volumeInfo.getDataStore();
+
+            String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
+            int primaryStorageDownloadWait = NumberUtils.toInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
+            CopyCommand copyCommand = new CopyCommand(volumeInfo.getTO(), templateInfo.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
+
+            try {
+                if (srcVolumeDetached) {
+                    _volumeService.grantAccess(volumeInfo, hostVO, srcDataStore);
+                }
+
+                Map<String, String> srcDetails = getVolumeDetails(volumeInfo);
+
+                copyCommand.setOptions(srcDetails);
+
+                copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
+
+                if (!copyCmdAnswer.getResult()) {
+                    // We were not able to copy. Handle it.
+                    errMsg = copyCmdAnswer.getDetails();
+                    throw new CloudRuntimeException(errMsg);
+                }
+
+                VMTemplateVO vmTemplateVO = _vmTemplateDao.findById(templateInfo.getId());
+
+                vmTemplateVO.setHypervisorType(HypervisorType.KVM);
+
+                _vmTemplateDao.update(vmTemplateVO.getId(), vmTemplateVO);
+            }
+            catch (CloudRuntimeException | AgentUnavailableException | OperationTimedoutException ex) {
+                String msg = "Failed to create template from volume (Volume ID = " + volumeInfo.getId() + ") : ";
+
+                LOGGER.warn(msg, ex);
+
+                throw new CloudRuntimeException(msg + ex.getMessage(), ex);
+            }
+            finally {
+                try {
+                    if (srcVolumeDetached) {
+                        _volumeService.revokeAccess(volumeInfo, hostVO, srcDataStore);
+                    }
+                }
+                catch (Exception ex) {
+                    LOGGER.warn("Error revoking access to volume (Volume ID = " + volumeInfo.getId() + "): " + ex.getMessage(), ex);
+                }
+                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
+                    if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
+                        errMsg = copyCmdAnswer.getDetails();
+                    }
+                    else {
+                        errMsg = "Unable to create template from volume";
+                    }
+                }
+
+                try {
+                    if (StringUtils.isEmpty(errMsg)) {
+                        volumeInfo.processEvent(Event.OperationSuccessed);
+                    }
+                    else {
+                        volumeInfo.processEvent(Event.OperationFailed);
+                    }
+                }
+                catch (Exception ex) {
+                    LOGGER.warn("Error processing snapshot event: " + ex.getMessage(), ex);
+                }
+            }
+        }
+        catch (Exception ex) {
+            errMsg = ex.getMessage();
+
+            throw new CloudRuntimeException(errMsg);
+        }
+        finally {
+            if (copyCmdAnswer == null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
 
-        long storagePoolId = volumeVO.getPoolId();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private Map<String, String> getVolumeDetails(VolumeInfo volumeInfo) {
+        long storagePoolId = volumeInfo.getPoolId();
         StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
 
+        if (!storagePoolVO.isManaged()) {
+            return null;
+        }
+
+        Map<String, String> volumeDetails = new HashMap<>();
+
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+
         volumeDetails.put(DiskTO.STORAGE_HOST, storagePoolVO.getHostAddress());
         volumeDetails.put(DiskTO.STORAGE_PORT, String.valueOf(storagePoolVO.getPort()));
         volumeDetails.put(DiskTO.IQN, volumeVO.get_iScsiName());
 
+        volumeDetails.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeVO.getSize()));
+        volumeDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, getVolumeProperty(volumeInfo.getId(), DiskTO.SCSI_NAA_DEVICE_ID));
+
         ChapInfo chapInfo = _volumeService.getChapInfo(volumeInfo, volumeInfo.getDataStore());
 
         if (chapInfo != null) {
@@ -836,34 +1891,59 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 
         long snapshotId = snapshotInfo.getId();
 
-        snapshotDetails.put(DiskTO.IQN, getProperty(snapshotId, DiskTO.IQN));
+        snapshotDetails.put(DiskTO.IQN, getSnapshotProperty(snapshotId, DiskTO.IQN));
+        snapshotDetails.put(DiskTO.VOLUME_SIZE, String.valueOf(snapshotInfo.getSize()));
+        snapshotDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, getSnapshotProperty(snapshotId, DiskTO.SCSI_NAA_DEVICE_ID));
 
-        snapshotDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, getProperty(snapshotId, DiskTO.CHAP_INITIATOR_USERNAME));
-        snapshotDetails.put(DiskTO.CHAP_INITIATOR_SECRET, getProperty(snapshotId, DiskTO.CHAP_INITIATOR_SECRET));
-        snapshotDetails.put(DiskTO.CHAP_TARGET_USERNAME, getProperty(snapshotId, DiskTO.CHAP_TARGET_USERNAME));
-        snapshotDetails.put(DiskTO.CHAP_TARGET_SECRET, getProperty(snapshotId, DiskTO.CHAP_TARGET_SECRET));
+        snapshotDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, getSnapshotProperty(snapshotId, DiskTO.CHAP_INITIATOR_USERNAME));
+        snapshotDetails.put(DiskTO.CHAP_INITIATOR_SECRET, getSnapshotProperty(snapshotId, DiskTO.CHAP_INITIATOR_SECRET));
+        snapshotDetails.put(DiskTO.CHAP_TARGET_USERNAME, getSnapshotProperty(snapshotId, DiskTO.CHAP_TARGET_USERNAME));
+        snapshotDetails.put(DiskTO.CHAP_TARGET_SECRET, getSnapshotProperty(snapshotId, DiskTO.CHAP_TARGET_SECRET));
 
         return snapshotDetails;
     }
 
     private HostVO getHost(SnapshotInfo snapshotInfo) {
-        HostVO hostVO = getHost(snapshotInfo.getDataCenterId(), true);
+        HypervisorType hypervisorType = snapshotInfo.getHypervisorType();
 
-        if (hostVO == null) {
-            hostVO = getHost(snapshotInfo.getDataCenterId(), false);
+        if (HypervisorType.XenServer.equals(hypervisorType)) {
+            HostVO hostVO = getHost(snapshotInfo.getDataCenterId(), hypervisorType, true);
 
             if (hostVO == null) {
-                throw new CloudRuntimeException("Unable to locate an applicable host in data center with ID = " + snapshotInfo.getDataCenterId());
+                hostVO = getHost(snapshotInfo.getDataCenterId(), hypervisorType, false);
+
+                if (hostVO == null) {
+                    throw new CloudRuntimeException("Unable to locate an applicable host in data center with ID = " + snapshotInfo.getDataCenterId());
+                }
             }
+
+            return hostVO;
+        }
+
+        if (HypervisorType.VMware.equals(hypervisorType) || HypervisorType.KVM.equals(hypervisorType)) {
+            return getHost(snapshotInfo.getDataCenterId(), hypervisorType, false);
+        }
+
+        throw new CloudRuntimeException("Unsupported hypervisor type");
+    }
+
+    private HostVO getHostInCluster(long clusterId) {
+        List<HostVO> hosts = _hostDao.findByClusterId(clusterId);
+
+        if (hosts != null && hosts.size() > 0) {
+            Collections.shuffle(hosts, RANDOM);
+
+            return hosts.get(0);
         }
 
-        return hostVO;
+        throw new CloudRuntimeException("Unable to locate a host");
     }
 
-    private HostVO getHost(Long zoneId, boolean computeClusterMustSupportResign) {
+    private HostVO getHost(Long zoneId, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) {
         Preconditions.checkArgument(zoneId != null, "Zone ID cannot be null.");
+        Preconditions.checkArgument(hypervisorType != null, "Hypervisor type cannot be null.");
 
-        List<HostVO> hosts = _hostDao.listByDataCenterIdAndHypervisorType(zoneId, HypervisorType.XenServer);
+        List<HostVO> hosts = _hostDao.listByDataCenterIdAndHypervisorType(zoneId, hypervisorType);
 
         if (hosts == null) {
             return null;
@@ -896,15 +1976,6 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         return null;
     }
 
-    @Override
-    public void copyAsync(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
-        CopyCommandResult result = new CopyCommandResult(null, null);
-
-        result.setResult("Unsupported operation requested for copying data.");
-
-        callback.complete(result);
-    }
-
     private Map<String, String> getDetails(DataObject dataObj) {
         if (dataObj instanceof VolumeInfo) {
             return getVolumeDetails((VolumeInfo)dataObj);
@@ -916,19 +1987,45 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         throw new CloudRuntimeException("'dataObj' must be of type 'VolumeInfo' or 'SnapshotInfo'.");
     }
 
-    private CopyCmdAnswer performResignature(DataObject dataObj, HostVO hostVO) {
-        return performResignature(dataObj, hostVO, false);
+    private boolean isForVMware(DataObject dataObj) {
+        if (dataObj instanceof VolumeInfo) {
+            return ImageFormat.OVA.equals(((VolumeInfo)dataObj).getFormat());
+        }
+
+        if (dataObj instanceof SnapshotInfo) {
+            return ImageFormat.OVA.equals(((SnapshotInfo)dataObj).getBaseVolume().getFormat());
+        }
+
+        return dataObj instanceof TemplateInfo && HypervisorType.VMware.equals(((TemplateInfo)dataObj).getHypervisorType());
+    }
+
+    private CopyCmdAnswer performResignature(DataObject dataObj, HostVO hostVO, Map<String, String> extraDetails) {
+        return performResignature(dataObj, hostVO, extraDetails, false);
     }
 
-    private CopyCmdAnswer performResignature(DataObject dataObj, HostVO hostVO, boolean keepGrantedAccess) {
+    private CopyCmdAnswer performResignature(DataObject dataObj, HostVO hostVO, Map<String, String> extraDetails, boolean keepGrantedAccess) {
         long storagePoolId = dataObj.getDataStore().getId();
         DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
 
         Map<String, String> details = getDetails(dataObj);
 
+        if (extraDetails != null) {
+            details.putAll(extraDetails);
+        }
+
         ResignatureCommand command = new ResignatureCommand(details);
 
-        ResignatureAnswer answer = null;
+        ResignatureAnswer answer;
+
+        GlobalLock lock = GlobalLock.getInternLock(dataStore.getUuid());
+
+        if (!lock.lock(LOCK_TIME_IN_SECONDS)) {
+            String errMsg = "Couldn't lock the DB (in performResignature) on the following string: " + dataStore.getUuid();
+
+            LOGGER.warn(errMsg);
+
+            throw new CloudRuntimeException(errMsg);
+        }
 
         try {
             _volumeService.grantAccess(dataObj, hostVO, dataStore);
@@ -945,7 +2042,10 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
             throw new CloudRuntimeException(msg + ex.getMessage());
         }
         finally {
-            if (keepGrantedAccess == false) {
+            lock.unlock();
+            lock.releaseRef();
+
+            if (!keepGrantedAccess) {
                 _volumeService.revokeAccess(dataObj, hostVO, dataStore);
             }
         }
@@ -972,19 +2072,148 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
         return new CopyCmdAnswer(newVolume);
     }
 
-    protected DataObject cacheSnapshotChain(SnapshotInfo snapshot, Scope scope) {
+    private DataObject cacheSnapshotChain(SnapshotInfo snapshot, Scope scope) {
         DataObject leafData = null;
         DataStore store = cacheMgr.getCacheStorage(snapshot, scope);
+
         while (snapshot != null) {
             DataObject cacheData = cacheMgr.createCacheObject(snapshot, store);
+
             if (leafData == null) {
                 leafData = cacheData;
             }
+
             snapshot = snapshot.getParent();
         }
+
         return leafData;
     }
 
+    private String migrateVolume(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
+        boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
+
+        try {
+            Map<String, String> srcDetails = getVolumeDetails(srcVolumeInfo);
+            Map<String, String> destDetails = getVolumeDetails(destVolumeInfo);
+
+            MigrateVolumeCommand migrateVolumeCommand = new MigrateVolumeCommand(srcVolumeInfo.getTO(), destVolumeInfo.getTO(),
+                    srcDetails, destDetails, StorageManager.KvmStorageOfflineMigrationWait.value());
+
+            if (srcVolumeDetached) {
+                _volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
+            }
+
+            _volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
+
+            MigrateVolumeAnswer migrateVolumeAnswer = (MigrateVolumeAnswer)_agentMgr.send(hostVO.getId(), migrateVolumeCommand);
+
+            if (migrateVolumeAnswer == null || !migrateVolumeAnswer.getResult()) {
+                if (migrateVolumeAnswer != null && !StringUtils.isEmpty(migrateVolumeAnswer.getDetails())) {
+                    throw new CloudRuntimeException(migrateVolumeAnswer.getDetails());
+                }
+                else {
+                    throw new CloudRuntimeException(errMsg);
+                }
+            }
+
+            if (srcVolumeDetached) {
+                _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
+            }
+
+            try {
+                _volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
+            }
+            catch (Exception e) {
+                // This volume should be deleted soon, so just log a warning here.
+                LOGGER.warn(e.getMessage(), e);
+            }
+
+            return migrateVolumeAnswer.getVolumePath();
+        }
+        catch (Exception ex) {
+            try {
+                _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
+            }
+            catch (Exception e) {
+                // This volume should be deleted soon, so just log a warning here.
+                LOGGER.warn(e.getMessage(), e);
+            }
+
+            if (srcVolumeDetached) {
+                _volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
+            }
+
+            String msg = "Failed to perform volume migration : ";
+
+            LOGGER.warn(msg, ex);
+
+            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
+        }
+    }
+
+    private String copyVolumeToSecondaryStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
+        boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
+
+        try {
+            StoragePoolVO storagePoolVO = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
+            Map<String, String> srcDetails = getVolumeDetails(srcVolumeInfo);
+
+            CopyVolumeCommand copyVolumeCommand = new CopyVolumeCommand(srcVolumeInfo.getId(), destVolumeInfo.getPath(), storagePoolVO,
+                    destVolumeInfo.getDataStore().getUri(), true, StorageManager.KvmStorageOfflineMigrationWait.value(), true);
+
+            copyVolumeCommand.setSrcData(srcVolumeInfo.getTO());
+            copyVolumeCommand.setSrcDetails(srcDetails);
+
+            if (srcVolumeDetached) {
+                _volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
+            }
+
+            CopyVolumeAnswer copyVolumeAnswer = (CopyVolumeAnswer)_agentMgr.send(hostVO.getId(), copyVolumeCommand);
+
+            if (copyVolumeAnswer == null || !copyVolumeAnswer.getResult()) {
+                if (copyVolumeAnswer != null && !StringUtils.isEmpty(copyVolumeAnswer.getDetails())) {
+                    throw new CloudRuntimeException(copyVolumeAnswer.getDetails());
+                }
+                else {
+                    throw new CloudRuntimeException(errMsg);
+                }
+            }
+
+            return copyVolumeAnswer.getVolumePath();
+        }
+        catch (Exception ex) {
+            String msg = "Failed to perform volume copy to secondary storage : ";
+
+            LOGGER.warn(msg, ex);
+
+            throw new CloudRuntimeException(msg + ex.getMessage());
+        }
+        finally {
+            if (srcVolumeDetached) {
+                _volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
+            }
+        }
+    }
+
+    private void setCertainVolumeValuesNull(long volumeId) {
+        VolumeVO volumeVO = _volumeDao.findById(volumeId);
+
+        volumeVO.set_iScsiName(null);
+        volumeVO.setMinIops(null);
+        volumeVO.setMaxIops(null);
+        volumeVO.setHypervisorSnapshotReserve(null);
+
+        _volumeDao.update(volumeId, volumeVO);
+    }
+
+    private void updateVolumePath(long volumeId, String path) {
+        VolumeVO volumeVO = _volumeDao.findById(volumeId);
+
+        volumeVO.setPath(path);
+
+        _volumeDao.update(volumeId, volumeVO);
+    }
+
     /**
      * Copies data from secondary storage to a primary volume
      * @param volumeInfo The primary volume
@@ -1033,7 +2262,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 
             LOGGER.warn(msg, ex);
 
-            throw new CloudRuntimeException(msg + ex.getMessage());
+            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
         }
         finally {
             if (Snapshot.LocationType.PRIMARY.equals(locationType)) {
diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
index 5a4eee4..88c385b 100644
--- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
@@ -17,12 +17,15 @@
 package org.apache.cloudstack.storage.snapshot;
 
 import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.ModifyTargetsCommand;
 import com.cloud.agent.api.to.DiskTO;
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.event.ActionEvent;
 import com.cloud.event.EventTypes;
 import com.cloud.event.UsageEventUtils;
 import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceAllocationException;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
@@ -46,10 +49,16 @@ import com.cloud.storage.VolumeDetailVO;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
+import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.snapshot.VMSnapshot;
+import com.cloud.vm.snapshot.VMSnapshotService;
+import com.cloud.vm.snapshot.VMSnapshotVO;
+import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
@@ -64,17 +73,21 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
 import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import javax.inject.Inject;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.UUID;
 
 @Component
 public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
@@ -89,14 +102,18 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
     @Inject private SnapshotDao snapshotDao;
     @Inject private SnapshotDataFactory snapshotDataFactory;
     @Inject private SnapshotDetailsDao snapshotDetailsDao;
+    @Inject SnapshotDataStoreDao snapshotStoreDao;
+    @Inject private VolumeDetailsDao volumeDetailsDao;
     @Inject private VMInstanceDao vmInstanceDao;
+    @Inject private VMSnapshotDao vmSnapshotDao;
+    @Inject private VMSnapshotService vmSnapshotService;
     @Inject private VolumeDao volumeDao;
     @Inject private VolumeService volService;
     @Inject private VolumeDetailsDao _volumeDetailsDaoImpl;
 
     @Override
     public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) {
-        Preconditions.checkArgument(snapshotInfo != null, "backupSnapshot expects a valid snapshot");
+        Preconditions.checkArgument(snapshotInfo != null, "'snapshotInfo' cannot be 'null'.");
 
         if (snapshotInfo.getLocationType() != Snapshot.LocationType.SECONDARY) {
             markAsBackedUp((SnapshotObject)snapshotInfo);
@@ -107,14 +124,24 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
         // At this point, the snapshot is either taken as a native
         // snapshot on the storage or exists as a volume on the storage (clone).
         // If archive flag is passed in, we should copy this snapshot to secondary
-        // storage and delete it from the primary storage.
+        // storage and delete it from primary storage.
 
         HostVO host = getHost(snapshotInfo.getVolumeId());
+
         boolean canStorageSystemCreateVolumeFromSnapshot = canStorageSystemCreateVolumeFromSnapshot(snapshotInfo.getBaseVolume().getPoolId());
+
+        if (!canStorageSystemCreateVolumeFromSnapshot) {
+            String msg = "Cannot archive snapshot: 'canStorageSystemCreateVolumeFromSnapshot' was false.";
+
+            s_logger.warn(msg);
+
+            throw new CloudRuntimeException(msg);
+        }
+
         boolean computeClusterSupportsResign = clusterDao.getSupportsResigning(host.getClusterId());
 
-        if (!canStorageSystemCreateVolumeFromSnapshot || !computeClusterSupportsResign) {
-            String msg = "Cannot archive snapshot: canStorageSystemCreateVolumeFromSnapshot and/or computeClusterSupportsResign were false.";
+        if (!computeClusterSupportsResign) {
+            String msg = "Cannot archive snapshot: 'computeClusterSupportsResign' was false.";
 
             s_logger.warn(msg);
 
@@ -126,6 +153,8 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
 
     @Override
     public boolean deleteSnapshot(Long snapshotId) {
+        Preconditions.checkArgument(snapshotId != null, "'snapshotId' cannot be 'null'.");
+
         SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
 
         if (Snapshot.State.Destroyed.equals(snapshotVO.getState())) {
@@ -139,23 +168,21 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
         }
 
         if (!Snapshot.State.BackedUp.equals(snapshotVO.getState())) {
-            throw new InvalidParameterValueException("Unable to delete snapshotshot " + snapshotId + " because it is in the following state: " + snapshotVO.getState());
+            throw new InvalidParameterValueException("Unable to delete snapshot '" + snapshotId +
+                    "' because it is in the following state: " + snapshotVO.getState());
         }
 
         return cleanupSnapshotOnPrimaryStore(snapshotId);
     }
 
     /**
-     * Cleans up a snapshot which was taken on a primary store. This function
-     * removes
+     * This cleans up a snapshot which was taken on a primary store.
      *
-     * @param snapshotId: ID of snapshot that needs to be removed
-     * @return true if snapshot is removed, false otherwise
+     * @param snapshotId: ID of snapshot to be removed
+     * @return true if snapshot is removed; else, false
      */
-
     @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_OFF_PRIMARY, eventDescription = "deleting snapshot", async = true)
     private boolean cleanupSnapshotOnPrimaryStore(long snapshotId) {
-
         SnapshotObject snapshotObj = (SnapshotObject)snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
 
         if (snapshotObj == null) {
@@ -167,13 +194,13 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
         }
 
         if (ObjectInDataStoreStateMachine.State.Copying.equals(snapshotObj.getStatus())) {
-            throw new InvalidParameterValueException("Unable to delete snapshotshot " + snapshotId + " because it is in the copying state.");
+            throw new InvalidParameterValueException("Unable to delete snapshot '" + snapshotId + "' because it is in the copying state");
         }
 
         try {
             snapshotObj.processEvent(Snapshot.Event.DestroyRequested);
-            List<VolumeDetailVO> volumesFromSnapshot;
-            volumesFromSnapshot = _volumeDetailsDaoImpl.findDetails("SNAPSHOT_ID", String.valueOf(snapshotId), null);
+
+            List<VolumeDetailVO> volumesFromSnapshot = _volumeDetailsDaoImpl.findDetails("SNAPSHOT_ID", String.valueOf(snapshotId), null);
 
             if (volumesFromSnapshot.size() > 0) {
                 try {
@@ -181,6 +208,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
                 } catch (NoTransitionException e1) {
                     s_logger.debug("Failed to change snapshot state: " + e1.toString());
                 }
+
                 throw new InvalidParameterValueException("Unable to perform delete operation, Snapshot with id: " + snapshotId + " is in use  ");
             }
         }
@@ -194,6 +222,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
             snapshotSvr.deleteSnapshot(snapshotObj);
 
             snapshotObj.processEvent(Snapshot.Event.OperationSucceeded);
+
             UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_OFF_PRIMARY, snapshotObj.getAccountId(), snapshotObj.getDataCenterId(), snapshotId,
                     snapshotObj.getName(), null, null, 0L, snapshotObj.getClass().getName(), snapshotObj.getUuid());
         }
@@ -209,12 +238,202 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
 
             return false;
         }
+
         return true;
     }
 
+    private boolean isAcceptableRevertFormat(VolumeVO volumeVO) {
+        return ImageFormat.VHD.equals(volumeVO.getFormat()) || ImageFormat.OVA.equals(volumeVO.getFormat()) || ImageFormat.QCOW2.equals(volumeVO.getFormat());
+    }
+
+    private void verifyFormat(VolumeInfo volumeInfo) {
+        ImageFormat imageFormat = volumeInfo.getFormat();
+
+        if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA && imageFormat != ImageFormat.QCOW2) {
+            throw new CloudRuntimeException("Only the following image types are currently supported: " +
+                    ImageFormat.VHD.toString() + ", " + ImageFormat.OVA.toString() + ", and " + ImageFormat.QCOW2);
+        }
+    }
+
+    private void verifyDiskTypeAndHypervisor(VolumeInfo volumeInfo) {
+        ImageFormat imageFormat = volumeInfo.getFormat();
+        Volume.Type volumeType = volumeInfo.getVolumeType();
+
+        if (ImageFormat.OVA.equals(imageFormat) && Volume.Type.ROOT.equals(volumeType)) {
+            throw new CloudRuntimeException("The hypervisor type is VMware and the disk type is ROOT. For this situation, " +
+                "recover the data on the snapshot by creating a new CloudStack volume from the corresponding volume snapshot.");
+        }
+    }
+
+    private void verifySnapshotType(SnapshotInfo snapshotInfo) {
+        if (snapshotInfo.getHypervisorType() == HypervisorType.KVM && snapshotInfo.getDataStore().getRole() != DataStoreRole.Primary) {
+            throw new CloudRuntimeException("For the KVM hypervisor type, you can only revert a volume to a snapshot state if the snapshot " +
+                "resides on primary storage. For other snapshot types, create a volume from the snapshot to recover its data.");
+        }
+    }
+
+    private void verifyLocationType(SnapshotInfo snapshotInfo) {
+        VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
+
+        if (snapshotInfo.getLocationType() == Snapshot.LocationType.SECONDARY && volumeInfo.getFormat() != ImageFormat.VHD) {
+            throw new CloudRuntimeException("Only the '" + ImageFormat.VHD + "' image type can be used when 'LocationType' is set to 'SECONDARY'.");
+        }
+    }
+
+    private boolean getHypervisorRequiresResignature(VolumeInfo volumeInfo) {
+        return ImageFormat.VHD.equals(volumeInfo.getFormat()) || ImageFormat.OVA.equals(volumeInfo.getFormat());
+    }
+
     @Override
-    public boolean revertSnapshot(SnapshotInfo snapshot) {
-        throw new UnsupportedOperationException("Reverting not supported. Create a template or volume based on the snapshot instead.");
+    public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
+        VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
+
+        verifyFormat(volumeInfo);
+
+        verifyDiskTypeAndHypervisor(volumeInfo);
+
+        verifySnapshotType(snapshotInfo);
+
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshotInfo.getId(), DataStoreRole.Primary);
+
+        if (snapshotStore != null) {
+            long snapshotStoragePoolId = snapshotStore.getDataStoreId();
+
+            if (!volumeInfo.getPoolId().equals(snapshotStoragePoolId)) {
+                String errMsg = "Storage pool mismatch";
+
+                s_logger.error(errMsg);
+
+                throw new CloudRuntimeException(errMsg);
+            }
+        }
+
+        boolean storageSystemSupportsCapability = storageSystemSupportsCapability(volumeInfo.getPoolId(),
+                DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
+
+        if (!storageSystemSupportsCapability) {
+            String errMsg = "Storage pool revert capability not supported";
+
+            s_logger.error(errMsg);
+
+            throw new CloudRuntimeException(errMsg);
+        }
+
+        SnapshotVO snapshotVO = snapshotDao.acquireInLockTable(snapshotInfo.getId());
+
+        if (snapshotVO == null) {
+            String errMsg = "Failed to acquire lock on the following snapshot: " + snapshotInfo.getId();
+
+            s_logger.error(errMsg);
+
+            throw new CloudRuntimeException(errMsg);
+        }
+
+        Long hostId = null;
+        boolean success = false;
+
+        try {
+            volumeInfo.stateTransit(Volume.Event.RevertSnapshotRequested);
+
+            if (getHypervisorRequiresResignature(volumeInfo)) {
+                hostId = getHostId(volumeInfo);
+
+                if (hostId != null) {
+                    HostVO hostVO = hostDao.findById(hostId);
+                    DataStore dataStore = dataStoreMgr.getDataStore(volumeInfo.getPoolId(), DataStoreRole.Primary);
+
+                    volService.revokeAccess(volumeInfo, hostVO, dataStore);
+
+                    modifyTarget(false, volumeInfo, hostId);
+                }
+            }
+
+            success = snapshotSvr.revertSnapshot(snapshotInfo);
+
+            if (!success) {
+                String errMsg = "Failed to revert a volume to a snapshot state";
+
+                s_logger.error(errMsg);
+
+                throw new CloudRuntimeException(errMsg);
+            }
+        }
+        finally {
+            if (getHypervisorRequiresResignature(volumeInfo)) {
+                if (hostId != null) {
+                    HostVO hostVO = hostDao.findById(hostId);
+                    DataStore dataStore = dataStoreMgr.getDataStore(volumeInfo.getPoolId(), DataStoreRole.Primary);
+
+                    volService.grantAccess(volumeInfo, hostVO, dataStore);
+
+                    modifyTarget(true, volumeInfo, hostId);
+                }
+            }
+
+            if (success) {
+                volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
+            }
+            else {
+                volumeInfo.stateTransit(Volume.Event.OperationFailed);
+            }
+
+            snapshotDao.releaseFromLockTable(snapshotInfo.getId());
+        }
+
+        return true;
+    }
+
+    private Long getHostId(VolumeInfo volumeInfo) {
+        VirtualMachine virtualMachine = volumeInfo.getAttachedVM();
+
+        if (virtualMachine == null) {
+            return null;
+        }
+
+        Long hostId = virtualMachine.getHostId();
+
+        if (hostId == null) {
+            hostId = virtualMachine.getLastHostId();
+        }
+
+        return hostId;
+    }
+
+    private void modifyTarget(boolean add, VolumeInfo volumeInfo, long hostId) {
+        StoragePoolVO storagePoolVO = storagePoolDao.findById(volumeInfo.getPoolId());
+
+        Map<String, String> details = new HashMap<>(3);
+
+        details.put(ModifyTargetsCommand.IQN, volumeInfo.get_iScsiName());
+        details.put(ModifyTargetsCommand.STORAGE_HOST, storagePoolVO.getHostAddress());
+        details.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePoolVO.getPort()));
+
+        List<Map<String, String>> targets = new ArrayList<>(1);
+
+        targets.add(details);
+
+        ModifyTargetsCommand cmd = new ModifyTargetsCommand();
+
+        cmd.setTargets(targets);
+        cmd.setApplyToAllHostsInCluster(true);
+        cmd.setAdd(add);
+        cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.BOTH);
+
+        sendModifyTargetsCommand(cmd, hostId);
+    }
+
+    private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
+        Answer answer = agentMgr.easySend(hostId, cmd);
+
+        if (answer == null) {
+            throw new CloudRuntimeException("Unable to get an answer to the modify targets command");
+        }
+
+        if (!answer.getResult()) {
+            String msg = "Unable to modify targets on the following host: " + hostId;
+
+            throw new CloudRuntimeException(msg);
+        }
     }
 
     @Override
@@ -222,8 +441,23 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
     public SnapshotInfo takeSnapshot(SnapshotInfo snapshotInfo) {
         VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
 
-        if (volumeInfo.getFormat() != ImageFormat.VHD) {
-            throw new CloudRuntimeException("Only the " + ImageFormat.VHD.toString() + " image type is currently supported.");
+        verifyFormat(volumeInfo);
+        verifyLocationType(snapshotInfo);
+
+        final boolean canStorageSystemCreateVolumeFromSnapshot = canStorageSystemCreateVolumeFromSnapshot(volumeInfo.getPoolId());
+        final boolean computeClusterSupportsVolumeClone;
+
+        // only XenServer, VMware and KVM are currently supported
+        if (volumeInfo.getFormat() == ImageFormat.VHD) {
+            HostVO hostVO = getHost(volumeInfo.getId());
+
+            computeClusterSupportsVolumeClone = clusterDao.getSupportsResigning(hostVO.getClusterId());
+        }
+        else if (volumeInfo.getFormat() == ImageFormat.OVA || volumeInfo.getFormat() == ImageFormat.QCOW2) {
+            computeClusterSupportsVolumeClone = true;
+        }
+        else {
+            throw new CloudRuntimeException("Unsupported format");
         }
 
         SnapshotVO snapshotVO = snapshotDao.acquireInLockTable(snapshotInfo.getId());
@@ -232,22 +466,33 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
             throw new CloudRuntimeException("Failed to acquire lock on the following snapshot: " + snapshotInfo.getId());
         }
 
+        VMSnapshot vmSnapshot = null;
+
+        if (ImageFormat.OVA.equals(volumeInfo.getFormat())) {
+            setVmdk(snapshotInfo, volumeInfo);
+
+            try {
+                vmSnapshot = takeHypervisorSnapshot(volumeInfo);
+            }
+            catch (ResourceAllocationException ex) {
+                String errMsg = "Unable to allocate VM snapshot";
+
+                s_logger.error(errMsg, ex);
+
+                throw new CloudRuntimeException(errMsg, ex);
+            }
+        }
+
         SnapshotResult result = null;
         SnapshotInfo snapshotOnPrimary = null;
 
         try {
             volumeInfo.stateTransit(Volume.Event.SnapshotRequested);
 
-            // only XenServer is currently supported
-            HostVO hostVO = getHost(volumeInfo.getId());
-
-            boolean canStorageSystemCreateVolumeFromSnapshot = canStorageSystemCreateVolumeFromSnapshot(volumeInfo.getPoolId());
-            boolean computeClusterSupportsResign = clusterDao.getSupportsResigning(hostVO.getClusterId());
-
-            // if canStorageSystemCreateVolumeFromSnapshot && computeClusterSupportsResign, then take a back-end snapshot or create a back-end clone;
+            // if canStorageSystemCreateVolumeFromSnapshot && computeClusterSupportsVolumeClone, then take a back-end snapshot or create a back-end clone;
             // else, just create a new back-end volume (eventually used to create a new SR on and to copy a VDI to)
 
-            if (canStorageSystemCreateVolumeFromSnapshot && computeClusterSupportsResign) {
+            if (canStorageSystemCreateVolumeFromSnapshot && computeClusterSupportsVolumeClone) {
                 SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(snapshotInfo.getId(),
                     "takeSnapshot",
                     Boolean.TRUE.toString(),
@@ -264,7 +509,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
                 throw new CloudRuntimeException(result.getResult());
             }
 
-            if (!canStorageSystemCreateVolumeFromSnapshot || !computeClusterSupportsResign) {
+            if (!canStorageSystemCreateVolumeFromSnapshot || !computeClusterSupportsVolumeClone) {
                 performSnapshotAndCopyOnHostSide(volumeInfo, snapshotInfo);
             }
 
@@ -276,6 +521,12 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
             } else {
                 volumeInfo.stateTransit(Volume.Event.OperationFailed);
             }
+
+            if (ImageFormat.OVA.equals(volumeInfo.getFormat())) {
+                if (vmSnapshot != null) {
+                    deleteHypervisorSnapshot(vmSnapshot);
+                }
+            }
         }
 
         snapshotDao.releaseFromLockTable(snapshotInfo.getId());
@@ -298,6 +549,68 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
 
     }
 
+    private VMSnapshot takeHypervisorSnapshot(VolumeInfo volumeInfo) throws ResourceAllocationException {
+        VirtualMachine virtualMachine = volumeInfo.getAttachedVM();
+
+        if (virtualMachine != null && VirtualMachine.State.Running.equals(virtualMachine.getState())) {
+            String vmSnapshotName = UUID.randomUUID().toString().replace("-", "");
+
+            VMSnapshotVO vmSnapshotVO =
+                    new VMSnapshotVO(virtualMachine.getAccountId(), virtualMachine.getDomainId(), virtualMachine.getId(), vmSnapshotName, vmSnapshotName,
+                            vmSnapshotName, virtualMachine.getServiceOfferingId(), VMSnapshot.Type.Disk, null);
+
+            VMSnapshot vmSnapshot = vmSnapshotDao.persist(vmSnapshotVO);
+
+            if (vmSnapshot == null) {
+                throw new CloudRuntimeException("Unable to allocate a VM snapshot object");
+            }
+
+            vmSnapshot = vmSnapshotService.createVMSnapshot(virtualMachine.getId(), vmSnapshot.getId(), true);
+
+            if (vmSnapshot == null) {
+                throw new CloudRuntimeException("Unable to create a hypervisor-side snapshot");
+            }
+
+            try {
+                Thread.sleep(60000);
+            }
+            catch (Exception ex) {
+                s_logger.warn(ex.getMessage(), ex);
+            }
+
+            return vmSnapshot;
+        }
+
+        // We didn't need to take a hypervisor-side snapshot. Return 'null' to indicate this.
+        return null;
+    }
+
+    private void deleteHypervisorSnapshot(VMSnapshot vmSnapshot) {
+        boolean success = vmSnapshotService.deleteVMSnapshot(vmSnapshot.getId());
+
+        if (!success) {
+            throw new CloudRuntimeException("Unable to delete the hypervisor-side snapshot");
+        }
+    }
+
+    private void setVmdk(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo) {
+        if (!ImageFormat.OVA.equals(volumeInfo.getFormat())) {
+            return;
+        }
+
+        String search = "]";
+
+        String path = volumeInfo.getPath();
+        int startIndex = path.indexOf(search);
+
+        SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(snapshotInfo.getId(),
+                DiskTO.VMDK,
+                path.substring(startIndex + search.length()).trim(),
+                false);
+
+        snapshotDetailsDao.persist(snapshotDetail);
+    }
+
     private void updateLocationTypeInDb(SnapshotInfo snapshotInfo) {
         Object objPayload = snapshotInfo.getPayload();
 
@@ -313,19 +626,23 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
     }
 
     private boolean canStorageSystemCreateVolumeFromSnapshot(long storagePoolId) {
-        boolean supportsCloningVolumeFromSnapshot = false;
+        return storageSystemSupportsCapability(storagePoolId, DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString());
+    }
+
+    private boolean storageSystemSupportsCapability(long storagePoolId, String capability) {
+        boolean supportsCapability = false;
 
         DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
 
         Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();
 
         if (mapCapabilities != null) {
-            String value = mapCapabilities.get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString());
+            String value = mapCapabilities.get(capability);
 
-            supportsCloningVolumeFromSnapshot = Boolean.valueOf(value);
+            supportsCapability = Boolean.valueOf(value);
         }
 
-        return supportsCloningVolumeFromSnapshot;
+        return supportsCapability;
     }
 
     private void performSnapshotAndCopyOnHostSide(VolumeInfo volumeInfo, SnapshotInfo snapshotInfo) {
@@ -385,7 +702,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
 
         SnapshotAndCopyCommand snapshotAndCopyCommand = new SnapshotAndCopyCommand(volumeInfo.getPath(), sourceDetails, destDetails);
 
-        SnapshotAndCopyAnswer snapshotAndCopyAnswer = null;
+        SnapshotAndCopyAnswer snapshotAndCopyAnswer;
 
         try {
             // if sourceDetails != null, we need to connect the host(s) to the volume
@@ -589,41 +906,104 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
         }
     }
 
+    private boolean usingBackendSnapshotFor(long snapshotId) {
+        String property = getProperty(snapshotId, "takeSnapshot");
+
+        return Boolean.parseBoolean(property);
+    }
+
     @Override
     public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
-        if (SnapshotOperation.REVERT.equals(op)) {
-            return StrategyPriority.CANT_HANDLE;
+        Snapshot.LocationType locationType = snapshot.getLocationType();
+
+        // If the snapshot exists on Secondary Storage, we can't delete it.
+        if (SnapshotOperation.DELETE.equals(op)) {
+            if (Snapshot.LocationType.SECONDARY.equals(locationType)) {
+                return StrategyPriority.CANT_HANDLE;
+            }
+
+            SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
+
+            // If the snapshot exists on Secondary Storage, we can't delete it.
+            if (snapshotStore != null) {
+                return StrategyPriority.CANT_HANDLE;
+            }
+
+            snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+
+            if (snapshotStore == null) {
+                return StrategyPriority.CANT_HANDLE;
+            }
+
+            long snapshotStoragePoolId = snapshotStore.getDataStoreId();
+
+            boolean storageSystemSupportsCapability = storageSystemSupportsCapability(snapshotStoragePoolId, DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
+
+            return storageSystemSupportsCapability ? StrategyPriority.HIGHEST : StrategyPriority.CANT_HANDLE;
         }
 
         long volumeId = snapshot.getVolumeId();
 
         VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
 
-        long storagePoolId = volumeVO.getPoolId();
+        long volumeStoragePoolId = volumeVO.getPoolId();
 
-        DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
+        if (SnapshotOperation.REVERT.equals(op)) {
+            boolean baseVolumeExists = volumeVO.getRemoved() == null;
 
-        Snapshot.LocationType locationType = snapshot.getLocationType();
+            if (baseVolumeExists) {
+                boolean acceptableFormat = isAcceptableRevertFormat(volumeVO);
 
-        // If the snapshot exists on Secondary Storage, we can't delete it.
-        if (SnapshotOperation.DELETE.equals(op) && Snapshot.LocationType.SECONDARY.equals(locationType)) {
-            return StrategyPriority.CANT_HANDLE;
-        }
+                if (acceptableFormat) {
+                    SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
 
-        if (dataStore != null) {
-            Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();
+                    boolean usingBackendSnapshot = usingBackendSnapshotFor(snapshot.getId());
 
-            if (mapCapabilities != null) {
-                String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
-                Boolean supportsStorageSystemSnapshots = Boolean.valueOf(value);
+                    if (usingBackendSnapshot) {
+                        if (snapshotStore != null) {
+                            long snapshotStoragePoolId = snapshotStore.getDataStoreId();
 
-                if (supportsStorageSystemSnapshots) {
-                    return StrategyPriority.HIGHEST;
+                            boolean storageSystemSupportsCapability = storageSystemSupportsCapability(snapshotStoragePoolId,
+                                    DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
+
+                            if (storageSystemSupportsCapability) {
+                                return StrategyPriority.HIGHEST;
+                            }
+
+                            storageSystemSupportsCapability = storageSystemSupportsCapability(volumeStoragePoolId,
+                                    DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
+
+                            if (storageSystemSupportsCapability) {
+                                return StrategyPriority.HIGHEST;
+                            }
+                        }
+                    }
+                    else {
+                        if (snapshotStore != null) {
+                            long snapshotStoragePoolId = snapshotStore.getDataStoreId();
+
+                            StoragePoolVO storagePoolVO = storagePoolDao.findById(snapshotStoragePoolId);
+
+                            if (storagePoolVO.isManaged()) {
+                                return StrategyPriority.HIGHEST;
+                            }
+                        }
+
+                        StoragePoolVO storagePoolVO = storagePoolDao.findById(volumeStoragePoolId);
+
+                        if (storagePoolVO.isManaged()) {
+                            return StrategyPriority.HIGHEST;
+                        }
+                    }
                 }
             }
+
+            return StrategyPriority.CANT_HANDLE;
         }
 
-        return StrategyPriority.CANT_HANDLE;
+        boolean storageSystemSupportsCapability = storageSystemSupportsCapability(volumeStoragePoolId, DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
+
+        return storageSystemSupportsCapability ? StrategyPriority.HIGHEST : StrategyPriority.CANT_HANDLE;
     }
 
 }
diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
index ad0418f..3631305 100644
--- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
+++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
@@ -71,7 +71,9 @@ import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
+import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.ModifyTargetsCommand;
 import com.cloud.agent.api.storage.ListVolumeAnswer;
 import com.cloud.agent.api.storage.ListVolumeCommand;
 import com.cloud.agent.api.storage.ResizeVolumeCommand;
@@ -124,6 +126,8 @@ import com.cloud.storage.dao.VolumeDetailsDao;
 public class VolumeServiceImpl implements VolumeService {
     private static final Logger s_logger = Logger.getLogger(VolumeServiceImpl.class);
     @Inject
+    protected AgentManager agentMgr;
+    @Inject
     VolumeDao volDao;
     @Inject
     PrimaryDataStoreProviderManager dataStoreMgr;
@@ -895,12 +899,12 @@ public class VolumeServiceImpl implements VolumeService {
             copyCaller.setCallback(copyCaller.getTarget().copyManagedTemplateCallback(null, null)).setContext(copyContext);
 
             // Populate details which will be later read by the storage subsystem.
-            Map<String, String> details = new HashMap<String, String>();
+            Map<String, String> details = new HashMap<>();
 
             details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
             details.put(PrimaryDataStore.STORAGE_HOST, destPrimaryDataStore.getHostAddress());
             details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(destPrimaryDataStore.getPort()));
-            details.put(PrimaryDataStore.MANAGED_STORE_TARGET, ((TemplateObject)templateOnPrimary).getInstallPath());
+            details.put(PrimaryDataStore.MANAGED_STORE_TARGET, templateOnPrimary.getInstallPath());
             details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, srcTemplateInfo.getUniqueName());
             details.put(PrimaryDataStore.REMOVE_AFTER_COPY, Boolean.TRUE.toString());
             details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(templateOnPrimary.getSize()));
@@ -920,7 +924,7 @@ public class VolumeServiceImpl implements VolumeService {
 
             grantAccess(templateOnPrimary, destHost, destPrimaryDataStore);
 
-            VolumeApiResult result = null;
+            VolumeApiResult result;
 
             try {
                 motionSrv.copyAsync(srcTemplateInfo, templateOnPrimary, destHost, copyCaller);
@@ -929,6 +933,16 @@ public class VolumeServiceImpl implements VolumeService {
             }
             finally {
                 revokeAccess(templateOnPrimary, destHost, destPrimaryDataStore);
+
+                if (HypervisorType.VMware.equals(destHost.getHypervisorType())) {
+                    details.put(ModifyTargetsCommand.IQN, templateOnPrimary.getInstallPath());
+
+                    List<Map<String, String>> targets = new ArrayList<>();
+
+                    targets.add(details);
+
+                    removeDynamicTargets(destHost.getId(), targets);
+                }
             }
 
             if (result.isFailed()) {
@@ -951,6 +965,32 @@ public class VolumeServiceImpl implements VolumeService {
         }
     }
 
+    private void removeDynamicTargets(long hostId, List<Map<String, String>> targets) {
+        ModifyTargetsCommand cmd = new ModifyTargetsCommand();
+
+        cmd.setTargets(targets);
+        cmd.setApplyToAllHostsInCluster(true);
+        cmd.setAdd(false);
+        cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
+
+        sendModifyTargetsCommand(cmd, hostId);
+    }
+
+    private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
+        Answer answer = agentMgr.easySend(hostId, cmd);
+
+        if (answer == null) {
+            String msg = "Unable to get an answer to the modify targets command";
+
+            s_logger.warn(msg);
+        }
+        else if (!answer.getResult()) {
+            String msg = "Unable to modify target on the following host: " + hostId;
+
+            s_logger.warn(msg);
+        }
+    }
+
     /**
      * Clones the template volume on managed storage to the ROOT volume
      *
@@ -1085,12 +1125,12 @@ public class VolumeServiceImpl implements VolumeService {
                 destPrimaryDataStore.getDriver().getCapabilities().get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString())
         );
 
-        boolean computeZoneSupportsResign = computeZoneSupportsResign(destHost.getDataCenterId(), destHost.getHypervisorType());
+        boolean computeSupportsVolumeClone = computeSupportsVolumeClone(destHost.getDataCenterId(), destHost.getHypervisorType());
 
         AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<>();
 
-        if (storageCanCloneVolume && computeZoneSupportsResign) {
-            s_logger.debug("Storage " + destDataStoreId + " can support cloning using a cached template and host cluster can perform UUID resigning.");
+        if (storageCanCloneVolume && computeSupportsVolumeClone) {
+            s_logger.debug("Storage " + destDataStoreId + " can support cloning using a cached template and compute side is OK with volume cloning.");
 
             TemplateInfo templateOnPrimary = destPrimaryDataStore.getTemplate(srcTemplateInfo.getId());
 
@@ -1118,16 +1158,22 @@ public class VolumeServiceImpl implements VolumeService {
 
             // We have a template on primary storage. Clone it to new volume.
             s_logger.debug("Creating a clone from template on primary storage " + destDataStoreId);
+
             createManagedVolumeCloneTemplateAsync(volumeInfo, templateOnPrimary, destPrimaryDataStore, future);
         } else {
             s_logger.debug("Primary storage does not support cloning or no support for UUID resigning on the host side; copying the template normally");
+
             createManagedVolumeCopyTemplateAsync(volumeInfo, destPrimaryDataStore, srcTemplateInfo, destHost, future);
         }
 
         return future;
     }
 
-    private boolean computeZoneSupportsResign(long zoneId, HypervisorType hypervisorType) {
+    private boolean computeSupportsVolumeClone(long zoneId, HypervisorType hypervisorType) {
+        if (HypervisorType.VMware.equals(hypervisorType) || HypervisorType.KVM.equals(hypervisorType)) {
+            return true;
+        }
+
         return getHost(zoneId, hypervisorType, true) != null;
     }
 
@@ -1757,7 +1803,17 @@ public class VolumeServiceImpl implements VolumeService {
         CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volume, future);
         AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
         caller.setCallback(caller.getTarget().resizeVolumeCallback(caller, context)).setContext(context);
-        volume.getDataStore().getDriver().resize(volume, caller);
+
+        try {
+            volume.getDataStore().getDriver().resize(volume, caller);
+        } catch (Exception e) {
+            s_logger.debug("Failed to change state to resize", e);
+
+            result.setResult(e.toString());
+
+            future.complete(result);
+        }
+
         return future;
     }
 
diff --git a/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java b/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java
index ba873aa..66b9228 100644
--- a/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java
+++ b/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java
@@ -25,6 +25,7 @@ import com.cloud.storage.dao.VolumeDetailsDao;
 import com.cloud.user.AccountDetailVO;
 import com.cloud.user.AccountDetailsDao;
 import com.cloud.utils.exception.CloudRuntimeException;
+
 import org.apache.cloudstack.storage.datastore.util.SolidFireUtil;
 import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil;
 import org.springframework.stereotype.Component;
@@ -46,9 +47,11 @@ public class SolidFireIntegrationTestManagerImpl implements SolidFireIntegration
         long storagePoolId = util.getStoragePoolIdForStoragePoolUuid(storagePoolUuid);
 
         AccountDetailVO accountDetail = accountDetailsDao.findDetail(csAccountId, SolidFireUtil.getAccountKey(storagePoolId));
+
         if (accountDetail == null){
             throw new CloudRuntimeException("Unable to find SF account for storage " + storagePoolUuid + " for CS account " + csAccountUuid);
         }
+
         String sfAccountId = accountDetail.getValue();
 
         return Long.parseLong(sfAccountId);
diff --git a/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java b/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java
index 427af11..4cbf74a 100644
--- a/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java
+++ b/plugins/api/solidfire-intg-test/src/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java
@@ -27,14 +27,16 @@ import com.cloud.storage.dao.VolumeDao;
 import com.cloud.user.Account;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.exception.CloudRuntimeException;
+
 import org.apache.cloudstack.api.response.solidfire.ApiVolumeSnapshotDetailsResponse;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 
-import javax.inject.Inject;
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 public class SolidFireIntegrationTestUtil {
     @Inject private AccountDao accountDao;
     @Inject private ClusterDao clusterDao;
@@ -47,15 +49,18 @@ public class SolidFireIntegrationTestUtil {
 
     public long getAccountIdForAccountUuid(String accountUuid) {
         Account account = accountDao.findByUuid(accountUuid);
-        if (account == null){
+
+        if (account == null) {
             throw new CloudRuntimeException("Unable to find Account for ID: " + accountUuid);
         }
+
         return account.getAccountId();
     }
 
     public long getAccountIdForVolumeUuid(String volumeUuid) {
         VolumeVO volume = volumeDao.findByUuid(volumeUuid);
-        if (volume == null){
+
+        if (volume == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + volumeUuid);
         }
 
@@ -64,15 +69,18 @@ public class SolidFireIntegrationTestUtil {
 
     public long getAccountIdForSnapshotUuid(String snapshotUuid) {
         SnapshotVO snapshot = snapshotDao.findByUuid(snapshotUuid);
-        if (snapshot == null){
+
+        if (snapshot == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + snapshotUuid);
         }
+
         return snapshot.getAccountId();
     }
 
     public long getClusterIdForClusterUuid(String clusterUuid) {
         ClusterVO cluster = clusterDao.findByUuid(clusterUuid);
-        if (cluster == null){
+
+        if (cluster == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + clusterUuid);
         }
 
@@ -81,7 +89,8 @@ public class SolidFireIntegrationTestUtil {
 
     public long getStoragePoolIdForStoragePoolUuid(String storagePoolUuid) {
         StoragePoolVO storagePool = storagePoolDao.findByUuid(storagePoolUuid);
-        if (storagePool == null){
+
+        if (storagePool == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + storagePoolUuid);
         }
 
@@ -90,7 +99,8 @@ public class SolidFireIntegrationTestUtil {
 
     public String getPathForVolumeUuid(String volumeUuid) {
         VolumeVO volume = volumeDao.findByUuid(volumeUuid);
-        if (volume == null){
+
+        if (volume == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + volumeUuid);
         }
 
@@ -99,7 +109,8 @@ public class SolidFireIntegrationTestUtil {
 
     public String getVolume_iScsiName(String volumeUuid) {
         VolumeVO volume = volumeDao.findByUuid(volumeUuid);
-        if (volume == null){
+
+        if (volume == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + volumeUuid);
         }
 
@@ -108,7 +119,8 @@ public class SolidFireIntegrationTestUtil {
 
     public List<ApiVolumeSnapshotDetailsResponse> getSnapshotDetails(String snapshotUuid) {
         SnapshotVO snapshot = snapshotDao.findByUuid(snapshotUuid);
-        if (snapshot == null){
+
+        if (snapshot == null) {
             throw new CloudRuntimeException("Unable to find Volume for ID: " + snapshotUuid);
         }
 
diff --git a/plugins/api/vmware-sioc/pom.xml b/plugins/api/vmware-sioc/pom.xml
new file mode 100644
index 0000000..2845c7c
--- /dev/null
+++ b/plugins/api/vmware-sioc/pom.xml
@@ -0,0 +1,47 @@
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cloud-plugin-api-vmware-sioc</artifactId>
+  <name>Apache CloudStack Plugin - API VMware SIOC</name>
+  <parent>
+    <groupId>org.apache.cloudstack</groupId>
+    <artifactId>cloudstack-plugins</artifactId>
+    <version>4.11.0.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+       <artifactId>cloud-plugin-hypervisor-vmware</artifactId>
+       <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>-Xmx1024m</argLine>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/plugins/api/vmware-sioc/resources/META-INF/cloudstack/vmware-sioc/module.properties b/plugins/api/vmware-sioc/resources/META-INF/cloudstack/vmware-sioc/module.properties
new file mode 100644
index 0000000..826e644
--- /dev/null
+++ b/plugins/api/vmware-sioc/resources/META-INF/cloudstack/vmware-sioc/module.properties
@@ -0,0 +1,18 @@
+# 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.
+name=vmware-sioc
+parent=api
\ No newline at end of file
diff --git a/plugins/api/vmware-sioc/resources/META-INF/cloudstack/vmware-sioc/spring-sioc-context.xml b/plugins/api/vmware-sioc/resources/META-INF/cloudstack/vmware-sioc/spring-sioc-context.xml
new file mode 100644
index 0000000..d87b2f5
--- /dev/null
+++ b/plugins/api/vmware-sioc/resources/META-INF/cloudstack/vmware-sioc/spring-sioc-context.xml
@@ -0,0 +1,33 @@
+<!--
+  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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+                      >
+
+      <bean id="apiSiocServiceImpl" class="org.apache.cloudstack.api.sioc.ApiSiocServiceImpl"/>
+      <bean id="siocManagerImpl" class="org.apache.cloudstack.sioc.SiocManagerImpl"/>
+
+</beans>
diff --git a/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java
new file mode 100644
index 0000000..d0561aa
--- /dev/null
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java
@@ -0,0 +1,105 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command.admin.sioc;
+
+import javax.inject.Inject;
+
+import org.apache.log4j.Logger;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.api.response.sioc.ApiUpdateSiocInfoResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.sioc.SiocManager;
+
+import com.cloud.user.Account;
+
+@APICommand(name = UpdateSiocInfoCmd.APINAME, description = "Update SIOC info", responseObject = ApiUpdateSiocInfoResponse.class,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        since = "4.11.0",
+        authorized = {RoleType.Admin})
+public class UpdateSiocInfoCmd extends BaseCmd {
+    private static final Logger s_logger = Logger.getLogger(UpdateSiocInfoCmd.class.getName());
+
+    public static final String APINAME = "updateSiocInfo";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "Zone ID", required = true)
+    private long zoneId;
+
+    @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "Storage Pool ID", required = true)
+    private long storagePoolId;
+
+    @Parameter(name = "sharespergb", type = CommandType.INTEGER, description = "Shares per GB", required = true)
+    private int sharesPerGB;
+
+    @Parameter(name = "limitiopspergb", type = CommandType.INTEGER, description = "Limit IOPS per GB", required = true)
+    private int limitIopsPerGB;
+
+    @Parameter(name = "iopsnotifythreshold", type = CommandType.INTEGER, description = "Notify if IOPS above this value", required = true)
+    private int iopsNotifyThreshold;
+
+    @Inject private SiocManager manager;
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Account account = CallContext.current().getCallingAccount();
+
+        if (account != null) {
+            return account.getId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
+    }
+
+    @Override
+    public void execute() {
+        s_logger.info("'UpdateSiocInfoCmd.execute' method invoked");
+
+        String msg = "Success";
+
+        try {
+            manager.updateSiocInfo(zoneId, storagePoolId, sharesPerGB, limitIopsPerGB, iopsNotifyThreshold);
+        }
+        catch (Exception ex) {
+            msg = ex.getMessage();
+        }
+
+        ApiUpdateSiocInfoResponse response = new ApiUpdateSiocInfoResponse(msg);
+
+        response.setResponseName(getCommandName());
+        response.setObjectName("apiupdatesiocinfo");
+
+        setResponseObject(response);
+    }
+}
diff --git a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/response/sioc/ApiUpdateSiocInfoResponse.java
similarity index 63%
copy from core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
copy to plugins/api/vmware-sioc/src/org/apache/cloudstack/api/response/sioc/ApiUpdateSiocInfoResponse.java
index 6b89654..eb47839 100644
--- a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/response/sioc/ApiUpdateSiocInfoResponse.java
@@ -1,4 +1,3 @@
-//
 // 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
@@ -15,28 +14,19 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
-
-package com.cloud.agent.api;
-
-import com.cloud.agent.api.to.VirtualMachineTO;
+package org.apache.cloudstack.api.response.sioc;
 
-public class PrepareForMigrationCommand extends Command {
-    VirtualMachineTO vm;
+import org.apache.cloudstack.api.BaseResponse;
 
-    protected PrepareForMigrationCommand() {
-    }
-
-    public PrepareForMigrationCommand(VirtualMachineTO vm) {
-        this.vm = vm;
-    }
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
 
-    public VirtualMachineTO getVirtualMachine() {
-        return vm;
-    }
+public class ApiUpdateSiocInfoResponse extends BaseResponse {
+    @SerializedName("msg")
+    @Param(description = "The return message from the operation ('Success' if successful)")
+    private String _msg;
 
-    @Override
-    public boolean executeInSequence() {
-        return true;
+    public ApiUpdateSiocInfoResponse(String msg) {
+        _msg = msg;
     }
 }
diff --git a/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/sioc/ApiSiocService.java
similarity index 84%
copy from core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
copy to plugins/api/vmware-sioc/src/org/apache/cloudstack/api/sioc/ApiSiocService.java
index c192e4a..7b622dc 100644
--- a/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/sioc/ApiSiocService.java
@@ -1,4 +1,3 @@
-//
 // 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
@@ -15,9 +14,9 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
+package org.apache.cloudstack.api.sioc;
 
-package com.cloud.agent.api;
+import com.cloud.utils.component.PluggableService;
 
-public class ModifyTargetsAnswer extends Answer {
+public interface ApiSiocService extends PluggableService {
 }
diff --git a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/sioc/ApiSiocServiceImpl.java
similarity index 61%
copy from core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
copy to plugins/api/vmware-sioc/src/org/apache/cloudstack/api/sioc/ApiSiocServiceImpl.java
index 6b89654..1a91fd9 100644
--- a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/api/sioc/ApiSiocServiceImpl.java
@@ -1,4 +1,3 @@
-//
 // 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
@@ -15,28 +14,24 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
+package org.apache.cloudstack.api.sioc;
 
-package com.cloud.agent.api;
+import java.util.List;
+import java.util.ArrayList;
 
-import com.cloud.agent.api.to.VirtualMachineTO;
+import org.apache.cloudstack.api.command.admin.sioc.UpdateSiocInfoCmd;
+import org.springframework.stereotype.Component;
 
-public class PrepareForMigrationCommand extends Command {
-    VirtualMachineTO vm;
-
-    protected PrepareForMigrationCommand() {
-    }
+import com.cloud.utils.component.AdapterBase;
 
-    public PrepareForMigrationCommand(VirtualMachineTO vm) {
-        this.vm = vm;
-    }
+@Component
+public class ApiSiocServiceImpl extends AdapterBase implements ApiSiocService {
+    @Override
+    public List<Class<?>> getCommands() {
+        List<Class<?>> cmdList = new ArrayList<Class<?>>();
 
-    public VirtualMachineTO getVirtualMachine() {
-        return vm;
-    }
+        cmdList.add(UpdateSiocInfoCmd.class);
 
-    @Override
-    public boolean executeInSequence() {
-        return true;
+        return cmdList;
     }
 }
diff --git a/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/sioc/SiocManager.java
similarity index 79%
copy from core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
copy to plugins/api/vmware-sioc/src/org/apache/cloudstack/sioc/SiocManager.java
index c192e4a..1bbfbc8 100644
--- a/core/src/com/cloud/agent/api/ModifyTargetsAnswer.java
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/sioc/SiocManager.java
@@ -1,4 +1,3 @@
-//
 // 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
@@ -15,9 +14,8 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
-
-package com.cloud.agent.api;
+package org.apache.cloudstack.sioc;
 
-public class ModifyTargetsAnswer extends Answer {
+public interface SiocManager {
+    void updateSiocInfo(long zoneId, long storagePoolId, int sharesPerGB, int limitIopsPerGB, int iopsNotifyThreshold) throws Exception;
 }
diff --git a/plugins/api/vmware-sioc/src/org/apache/cloudstack/sioc/SiocManagerImpl.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/sioc/SiocManagerImpl.java
new file mode 100644
index 0000000..966c837
--- /dev/null
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/sioc/SiocManagerImpl.java
@@ -0,0 +1,463 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.sioc;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.util.LoginInfo;
+import org.apache.cloudstack.util.vmware.VMwareUtil;
+import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
+import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
+import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
+import com.cloud.hypervisor.vmware.mo.VirtualMachineDiskInfoBuilder;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.dao.VMInstanceDao;
+
+import com.vmware.vim25.ManagedObjectReference;
+import com.vmware.vim25.SharesInfo;
+import com.vmware.vim25.SharesLevel;
+import com.vmware.vim25.StorageIOAllocationInfo;
+import com.vmware.vim25.VirtualDevice;
+import com.vmware.vim25.VirtualDeviceConfigSpec;
+import com.vmware.vim25.VirtualDeviceConfigSpecOperation;
+import com.vmware.vim25.VirtualDisk;
+import com.vmware.vim25.VirtualDeviceFileBackingInfo;
+import com.vmware.vim25.VirtualMachineConfigInfo;
+import com.vmware.vim25.VirtualMachineConfigSpec;
+
+@Component
+public class SiocManagerImpl implements SiocManager {
+    private static final Logger LOGGER = Logger.getLogger(SiocManagerImpl.class);
+    private static final int LOCK_TIME_IN_SECONDS = 3;
+    private static final int ONE_GB_IN_BYTES = 1000000000;
+    private static final int LOWEST_SHARES_PER_VIRTUAL_DISK = 2000; // We want this to be greater than 1,000, which is the VMware default value.
+    private static final int HIGHEST_SHARES_PER_VIRTUAL_DISK = 4000; // VMware limit
+    private static final int LOWEST_LIMIT_IOPS_PER_VIRTUAL_DISK = 16; // VMware limit
+    private static final int HIGHEST_LIMIT_IOPS_PER_VIRTUAL_DISK = 2147483647; // VMware limit
+
+    @Inject private DataCenterDao zoneDao;
+    @Inject private DiskOfferingDao diskOfferingDao;
+    @Inject private PrimaryDataStoreDao storagePoolDao;
+    @Inject private StoragePoolDetailsDao storagePoolDetailsDao;
+    @Inject private VMInstanceDao vmInstanceDao;
+    @Inject private VmwareDatacenterDao vmwareDcDao;
+    @Inject private VmwareDatacenterZoneMapDao vmwareDcZoneMapDao;
+    @Inject private VolumeDao volumeDao;
+
+    @Override
+    public void updateSiocInfo(long zoneId, long storagePoolId, int sharesPerGB, int limitIopsPerGB, int iopsNotifyThreshold) throws Exception {
+        LOGGER.info("'SiocManagerImpl.updateSiocInfo(long, long, int, int, int)' method invoked");
+
+        DataCenterVO zone = zoneDao.findById(zoneId);
+
+        if (zone == null) {
+            throw new Exception("Error: No zone could be located for the following zone ID: " + zoneId + ".");
+        }
+
+        StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
+
+        if (storagePool == null) {
+            throw new Exception("Error: No storage pool could be located for the following pool ID: " + storagePoolId + ".");
+        }
+
+        if (storagePool.getDataCenterId() != zoneId) {
+            throw new Exception("Error: Storage pool '" + storagePool.getName() + "' is not in zone ID " + zoneId + ".");
+        }
+
+        if (!storagePool.getPoolType().equals(StoragePoolType.VMFS)) {
+            throw new Exception("Error: Storage pool '" + storagePool.getName() + "' does not represent a VMFS datastore.");
+        }
+
+        String lockName = zone.getUuid() + "-" + storagePool.getUuid();
+        GlobalLock lock = GlobalLock.getInternLock(lockName);
+
+        if (!lock.lock(LOCK_TIME_IN_SECONDS)) {
+            throw new Exception("Busy: The system is already processing this request.");
+        }
+
+        VMwareUtil.VMwareConnection connection = null;
+
+        try {
+            connection = VMwareUtil.getVMwareConnection(getLoginInfo(zoneId));
+
+            Map<String, ManagedObjectReference> nameToVm = VMwareUtil.getVms(connection);
+
+            List<ManagedObjectReference> allTasks = new ArrayList<>();
+
+            int limitIopsTotal = 0;
+
+            List<VolumeVO> volumes = volumeDao.findByPoolId(storagePoolId, null);
+
+            if (volumes != null && volumes.size() > 0) {
+                Set<Long> instanceIds = new HashSet<>();
+
+                for (VolumeVO volume : volumes) {
+                    Long instanceId = volume.getInstanceId();
+
+                    if (instanceId != null) {
+                        instanceIds.add(instanceId);
+                    }
+                }
+
+                for (Long instanceId : instanceIds) {
+                    ResultWrapper resultWrapper = updateSiocInfo(connection, nameToVm, instanceId, storagePool, sharesPerGB, limitIopsPerGB);
+
+                    limitIopsTotal += resultWrapper.getLimitIopsTotal();
+
+                    allTasks.addAll(resultWrapper.getTasks());
+                }
+            }
+            /*
+            Set<String> vmNames = nameToVm.keySet();
+
+            for (String vmName : vmNames) {
+                // If the VM's name doesn't start with "i-", then it should be a worker VM (which is not stored in the CloudStack datastore).
+                if (!vmName.startsWith("i-")) {
+                    ResultWrapper resultWrapper = updateSiocInfoForWorkerVM(connection, nameToVm.get(vmName),
+                            getDatastoreName(storagePool.getPath()), limitIopsPerGB);
+
+                    limitIopsTotal += resultWrapper.getLimitIopsTotal();
+
+                    allTasks.addAll(resultWrapper.getTasks());
+                }
+            }
+            */
+            for (ManagedObjectReference task : allTasks) {
+                VMwareUtil.waitForTask(connection, task);
+            }
+
+            if (limitIopsTotal > iopsNotifyThreshold) {
+                throw new Exception("Warning: Total number of IOPS: " + limitIopsTotal + "; IOPS notify threshold: " + iopsNotifyThreshold);
+            }
+        }
+        finally {
+            VMwareUtil.closeVMwareConnection(connection);
+
+            lock.unlock();
+            lock.releaseRef();
+        }
+    }
+
+    private ResultWrapper updateSiocInfo(VMwareUtil.VMwareConnection connection, Map<String, ManagedObjectReference> nameToVm, Long instanceId,
+                                         StoragePoolVO storagePool, int sharesPerGB, int limitIopsPerGB) throws Exception {
+        int limitIopsTotal = 0;
+        List<ManagedObjectReference> tasks = new ArrayList<>();
+
+        VMInstanceVO vmInstance = vmInstanceDao.findById(instanceId);
+
+        if (vmInstance == null) {
+            String errMsg = "Error: The VM with ID " + instanceId + " could not be located.";
+
+            throw new Exception(errMsg);
+        }
+
+        String vmName = vmInstance.getInstanceName();
+
+        ManagedObjectReference morVm = nameToVm.get(vmName);
+
+        if (morVm == null) {
+            String errMsg = "Error: The VM with ID " + instanceId + " could not be located (ManagedObjectReference).";
+
+            throw new Exception(errMsg);
+        }
+
+        VirtualMachineConfigInfo vmci = (VirtualMachineConfigInfo)VMwareUtil.getEntityProps(connection, morVm,
+                new String[] { "config" }).get("config");
+        List<VirtualDevice> devices = vmci.getHardware().getDevice();
+
+        for (VirtualDevice device : devices) {
+            if (device instanceof VirtualDisk) {
+                VirtualDisk disk = (VirtualDisk)device;
+
+                VolumeVO volumeVO = getVolumeFromVirtualDisk(vmInstance, storagePool.getId(), devices, disk);
+
+                if (volumeVO != null) {
+                    boolean diskUpdated = false;
+
+                    StorageIOAllocationInfo sioai = disk.getStorageIOAllocation();
+
+                    SharesInfo sharesInfo = sioai.getShares();
+
+                    int currentShares = sharesInfo.getShares();
+                    int newShares = getNewSharesBasedOnVolumeSize(volumeVO, sharesPerGB);
+
+                    if (currentShares != newShares) {
+                        sharesInfo.setLevel(SharesLevel.CUSTOM);
+                        sharesInfo.setShares(newShares);
+
+                        diskUpdated = true;
+                    }
+
+                    long currentLimitIops = sioai.getLimit() !=  null ? sioai.getLimit() : Long.MIN_VALUE;
+                    long newLimitIops = getNewLimitIopsBasedOnVolumeSize(volumeVO, limitIopsPerGB);
+
+                    limitIopsTotal += newLimitIops;
+
+                    if (currentLimitIops != newLimitIops) {
+                        sioai.setLimit(newLimitIops);
+
+                        diskUpdated = true;
+                    }
+
+                    if (diskUpdated) {
+                        VirtualDeviceConfigSpec vdcs = new VirtualDeviceConfigSpec();
+
+                        vdcs.setDevice(disk);
+                        vdcs.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
+
+                        VirtualMachineConfigSpec vmcs = new VirtualMachineConfigSpec();
+
+                        vmcs.getDeviceChange().add(vdcs);
+
+                        try {
+                            ManagedObjectReference task = VMwareUtil.reconfigureVm(connection, morVm, vmcs);
+
+                            tasks.add(task);
+
+                            LOGGER.info(getInfoMsg(volumeVO, newShares, newLimitIops));
+                        } catch (Exception ex) {
+                            throw new Exception("Error: " + ex.getMessage());
+                        }
+                    }
+                }
+            }
+        }
+
+        return new ResultWrapper(limitIopsTotal, tasks);
+    }
+
+    private String getDatastoreName(String path) throws Exception {
+        String searchString = "/";
+
+        int lastIndexOf = path.lastIndexOf(searchString);
+
+        if (lastIndexOf == -1) {
+            throw new Exception("Error: Invalid datastore path");
+        }
+
+        return path.substring(lastIndexOf + searchString.length());
+    }
+
+    private ResultWrapper updateSiocInfoForWorkerVM(VMwareUtil.VMwareConnection connection, ManagedObjectReference morVm, String datastoreName,
+                                                    int limitIopsPerGB) throws Exception {
+        int limitIopsTotal = 0;
+        List<ManagedObjectReference> tasks = new ArrayList<>();
+
+        VirtualMachineConfigInfo vmci = (VirtualMachineConfigInfo)VMwareUtil.getEntityProps(connection, morVm,
+                new String[] { "config" }).get("config");
+        List<VirtualDevice> devices = vmci.getHardware().getDevice();
+
+        for (VirtualDevice device : devices) {
+            if (device instanceof VirtualDisk) {
+                VirtualDisk disk = (VirtualDisk)device;
+
+                if (disk.getBacking() instanceof VirtualDeviceFileBackingInfo) {
+                    VirtualDeviceFileBackingInfo backingInfo = (VirtualDeviceFileBackingInfo)disk.getBacking();
+
+                    if (backingInfo.getFileName().contains(datastoreName)) {
+                        boolean diskUpdated = false;
+
+                        StorageIOAllocationInfo sioai = disk.getStorageIOAllocation();
+
+                        long currentLimitIops = sioai.getLimit() !=  null ? sioai.getLimit() : Long.MIN_VALUE;
+                        long newLimitIops = getNewLimitIopsBasedOnVolumeSize(disk.getCapacityInBytes(), limitIopsPerGB);
+
+                        limitIopsTotal += newLimitIops;
+
+                        if (currentLimitIops != newLimitIops) {
+                            sioai.setLimit(newLimitIops);
+
+                            diskUpdated = true;
+                        }
+
+                        if (diskUpdated) {
+                            VirtualDeviceConfigSpec vdcs = new VirtualDeviceConfigSpec();
+
+                            vdcs.setDevice(disk);
+                            vdcs.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
+
+                            VirtualMachineConfigSpec vmcs = new VirtualMachineConfigSpec();
+
+                            vmcs.getDeviceChange().add(vdcs);
+
+                            try {
+                                ManagedObjectReference task = VMwareUtil.reconfigureVm(connection, morVm, vmcs);
+
+                                tasks.add(task);
+
+                                LOGGER.info(getInfoMsgForWorkerVm(newLimitIops));
+                            } catch (Exception ex) {
+                                throw new Exception("Error: " + ex.getMessage());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return new ResultWrapper(limitIopsTotal, tasks);
+    }
+
+    private String getInfoMsg(Volume volume, Integer newShares, Long newLimitIops) {
+        String msgPrefix = "VMware SIOC: Volume = " + volume.getName();
+
+        String msgNewShares = newShares != null ? "; New Shares = " + newShares : "";
+
+        String msgNewLimitIops = newLimitIops != null ? "; New Limit IOPS = " + newLimitIops : "";
+
+        return msgPrefix + msgNewShares + msgNewLimitIops;
+    }
+
+    private String getInfoMsgForWorkerVm(Long newLimitIops) {
+        return "VMware SIOC: Worker VM's Limit IOPS set to " + newLimitIops;
+    }
+
+    private VolumeVO getVolumeFromVirtualDisk(VMInstanceVO vmInstance, long storagePoolId, List<VirtualDevice> allDevices,
+            VirtualDisk disk) throws Exception {
+        List<VolumeVO> volumes = volumeDao.findByInstance(vmInstance.getId());
+
+        if (volumes == null || volumes.size() == 0) {
+            String errMsg = "Error: The VMware virtual disk '" + disk + "' could not be mapped to a CloudStack volume. " +
+                    "There were no volumes for the VM with the following ID: " + vmInstance.getId() + ".";
+
+            throw new Exception(errMsg);
+        }
+
+        VirtualMachineDiskInfoBuilder diskInfoBuilder = VMwareUtil.getDiskInfoBuilder(allDevices);
+
+        for (VolumeVO volume : volumes) {
+            Long poolId = volume.getPoolId();
+
+            if (poolId != null && poolId == storagePoolId) {
+                StoragePoolVO storagePool = storagePoolDao.findById(poolId);
+                String path = storagePool.getPath();
+                String charToSearchFor = "/";
+                int index = path.lastIndexOf(charToSearchFor) + charToSearchFor.length();
+                String datastoreName = path.substring(index);
+                VirtualMachineDiskInfo diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(volume.getPath(), datastoreName);
+
+                if (diskInfo != null) {
+                    String deviceBusName = VMwareUtil.getDeviceBusName(allDevices, disk);
+
+                    if (deviceBusName.equals(diskInfo.getDiskDeviceBusName())) {
+                        return volume;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private int getNewSharesBasedOnVolumeSize(VolumeVO volumeVO, int sharesPerGB) {
+        long volumeSizeInBytes = getVolumeSizeInBytes(volumeVO);
+
+        double sizeInGB = volumeSizeInBytes / (double)ONE_GB_IN_BYTES;
+
+        int shares = LOWEST_SHARES_PER_VIRTUAL_DISK + ((int)(sharesPerGB * sizeInGB));
+
+        return getAdjustedShares(shares);
+    }
+
+    private int getAdjustedShares(int shares) {
+        shares = Math.max(shares, LOWEST_SHARES_PER_VIRTUAL_DISK);
+        shares = Math.min(shares, HIGHEST_SHARES_PER_VIRTUAL_DISK);
+
+        return shares;
+    }
+
+    private long getNewLimitIopsBasedOnVolumeSize(VolumeVO volumeVO, int limitIopsPerGB) {
+        long volumeSizeInBytes = getVolumeSizeInBytes(volumeVO);
+
+        return getNewLimitIopsBasedOnVolumeSize(volumeSizeInBytes, limitIopsPerGB);
+    }
+
+    private long getNewLimitIopsBasedOnVolumeSize(Long volumeSizeInBytes, int limitIopsPerGB) {
+        if (volumeSizeInBytes == null) {
+            volumeSizeInBytes = (long)ONE_GB_IN_BYTES;
+        }
+
+        double sizeInGB = volumeSizeInBytes / (double)ONE_GB_IN_BYTES;
+
+        long limitIops = (long)(limitIopsPerGB * sizeInGB);
+
+        return getAdjustedLimitIops(limitIops);
+    }
+
+    private long getAdjustedLimitIops(long limitIops) {
+        limitIops = Math.max(limitIops, LOWEST_LIMIT_IOPS_PER_VIRTUAL_DISK);
+        limitIops = Math.min(limitIops, HIGHEST_LIMIT_IOPS_PER_VIRTUAL_DISK);
+
+        return limitIops;
+    }
+
+    private long getVolumeSizeInBytes(VolumeVO volumeVO) {
+        return volumeVO.getSize() != null && volumeVO.getSize() > ONE_GB_IN_BYTES ? volumeVO.getSize() : ONE_GB_IN_BYTES;
+    }
+
+    private LoginInfo getLoginInfo(long zoneId) {
+        VmwareDatacenterZoneMapVO vmwareDcZoneMap = vmwareDcZoneMapDao.findByZoneId(zoneId);
+        Long associatedVmwareDcId = vmwareDcZoneMap.getVmwareDcId();
+        VmwareDatacenterVO associatedVmwareDc = vmwareDcDao.findById(associatedVmwareDcId);
+
+        String host = associatedVmwareDc.getVcenterHost();
+        String username = associatedVmwareDc.getUser();
+        String password = associatedVmwareDc.getPassword();
+
+        return new LoginInfo(host, username, password);
+    }
+}
+
+class ResultWrapper {
+    private int limitIopsTotal;
+    private List<ManagedObjectReference> tasks;
+
+    ResultWrapper(int limitIopsTotal, List<ManagedObjectReference> tasks) {
+        this.limitIopsTotal = limitIopsTotal;
+        this.tasks = tasks;
+    }
+
+    int getLimitIopsTotal() {
+        return limitIopsTotal;
+    }
+
+    List<ManagedObjectReference> getTasks() {
+        return tasks;
+    }
+}
diff --git a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/util/LoginInfo.java
similarity index 62%
copy from core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
copy to plugins/api/vmware-sioc/src/org/apache/cloudstack/util/LoginInfo.java
index 6b89654..1929966 100644
--- a/core/src/com/cloud/agent/api/PrepareForMigrationCommand.java
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/util/LoginInfo.java
@@ -1,4 +1,3 @@
-//
 // 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
@@ -15,28 +14,28 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
-
-package com.cloud.agent.api;
-
-import com.cloud.agent.api.to.VirtualMachineTO;
+package org.apache.cloudstack.util;
 
-public class PrepareForMigrationCommand extends Command {
-    VirtualMachineTO vm;
+public class LoginInfo {
+    private final String _host;
+    private final String _username;
+    private final String _password;
 
-    protected PrepareForMigrationCommand() {
+    public LoginInfo(String host, String username, String password) {
+        _host = host;
+        _username = username;
+        _password = password;
     }
 
-    public PrepareForMigrationCommand(VirtualMachineTO vm) {
-        this.vm = vm;
+    public String getHost() {
+        return _host;
     }
 
-    public VirtualMachineTO getVirtualMachine() {
-        return vm;
+    public String getUsername() {
+        return _username;
     }
 
-    @Override
-    public boolean executeInSequence() {
-        return true;
+    public String getPassword() {
+        return _password;
     }
 }
diff --git a/plugins/api/vmware-sioc/src/org/apache/cloudstack/util/vmware/VMwareUtil.java b/plugins/api/vmware-sioc/src/org/apache/cloudstack/util/vmware/VMwareUtil.java
new file mode 100644
index 0000000..209945f
--- /dev/null
+++ b/plugins/api/vmware-sioc/src/org/apache/cloudstack/util/vmware/VMwareUtil.java
@@ -0,0 +1,570 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.util.vmware;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.WebServiceException;
+
+import org.apache.cloudstack.util.LoginInfo;
+import org.apache.log4j.Logger;
+
+import com.cloud.hypervisor.vmware.mo.VirtualMachineDiskInfoBuilder;
+import com.vmware.vim25.DynamicProperty;
+import com.vmware.vim25.InvalidCollectorVersionFaultMsg;
+import com.vmware.vim25.InvalidPropertyFaultMsg;
+import com.vmware.vim25.LocalizedMethodFault;
+import com.vmware.vim25.ManagedObjectReference;
+import com.vmware.vim25.ObjectContent;
+import com.vmware.vim25.ObjectSpec;
+import com.vmware.vim25.ObjectUpdate;
+import com.vmware.vim25.ObjectUpdateKind;
+import com.vmware.vim25.PropertyChange;
+import com.vmware.vim25.PropertyChangeOp;
+import com.vmware.vim25.PropertyFilterSpec;
+import com.vmware.vim25.PropertyFilterUpdate;
+import com.vmware.vim25.PropertySpec;
+import com.vmware.vim25.RetrieveOptions;
+import com.vmware.vim25.RetrieveResult;
+import com.vmware.vim25.RuntimeFaultFaultMsg;
+import com.vmware.vim25.SelectionSpec;
+import com.vmware.vim25.ServiceContent;
+import com.vmware.vim25.TaskInfoState;
+import com.vmware.vim25.TraversalSpec;
+import com.vmware.vim25.UpdateSet;
+import com.vmware.vim25.VimPortType;
+import com.vmware.vim25.VimService;
+import com.vmware.vim25.VirtualDevice;
+import com.vmware.vim25.VirtualDeviceBackingInfo;
+import com.vmware.vim25.VirtualDisk;
+import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
+import com.vmware.vim25.VirtualIDEController;
+import com.vmware.vim25.VirtualMachineConfigSpec;
+import com.vmware.vim25.VirtualSCSIController;
+
+public class VMwareUtil {
+    private static final Logger s_logger = Logger.getLogger(VMwareUtil.class);
+
+    private VMwareUtil() {}
+
+    public static class VMwareConnection {
+        private VimPortType _vimPortType;
+        private ServiceContent _serviceContent;
+
+        VMwareConnection(VimPortType vimPortType, ServiceContent serviceContent) {
+            _vimPortType = vimPortType;
+            _serviceContent = serviceContent;
+        }
+
+        VimPortType getVimPortType() {
+            return _vimPortType;
+        }
+
+        ServiceContent getServiceContent() {
+            return _serviceContent;
+        }
+    }
+
+    public static VMwareConnection getVMwareConnection(LoginInfo loginInfo) throws Exception {
+        trustAllHttpsCertificates();
+
+        HostnameVerifier hv = new HostnameVerifier() {
+            @Override
+            public boolean verify(String urlHostName, SSLSession session) {
+                return true;
+            }
+        };
+
+        HttpsURLConnection.setDefaultHostnameVerifier(hv);
+
+        ManagedObjectReference serviceInstanceRef = new ManagedObjectReference();
+
+        final String serviceInstanceName = "ServiceInstance";
+
+        serviceInstanceRef.setType(serviceInstanceName);
+        serviceInstanceRef.setValue(serviceInstanceName);
+
+        VimService vimService = new VimService();
+
+        VimPortType vimPortType = vimService.getVimPort();
+
+        Map<String, Object> ctxt = ((BindingProvider)vimPortType).getRequestContext();
+
+        ctxt.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://" + loginInfo.getHost() + "/sdk");
+        ctxt.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);
+
+        ServiceContent serviceContent = vimPortType.retrieveServiceContent(serviceInstanceRef);
+
+        vimPortType.login(serviceContent.getSessionManager(), loginInfo.getUsername(), loginInfo.getPassword(), null);
+
+        return new VMwareConnection(vimPortType, serviceContent);
+    }
+
+    public static void closeVMwareConnection(VMwareConnection connection) throws Exception {
+        if (connection != null) {
+            connection.getVimPortType().logout(connection.getServiceContent().getSessionManager());
+        }
+    }
+
+    public static Map<String, ManagedObjectReference> getVms(VMwareConnection connection) throws Exception {
+        Map<String, ManagedObjectReference> nameToVm = new HashMap<>();
+
+        ManagedObjectReference rootFolder = connection.getServiceContent().getRootFolder();
+
+        TraversalSpec tSpec = getVMTraversalSpec();
+
+        PropertySpec propertySpec = new PropertySpec();
+
+        propertySpec.setAll(Boolean.FALSE);
+        propertySpec.getPathSet().add("name");
+        propertySpec.setType("VirtualMachine");
+
+        ObjectSpec objectSpec = new ObjectSpec();
+
+        objectSpec.setObj(rootFolder);
+        objectSpec.setSkip(Boolean.TRUE);
+        objectSpec.getSelectSet().add(tSpec);
+
+        PropertyFilterSpec propertyFilterSpec = new PropertyFilterSpec();
+
+        propertyFilterSpec.getPropSet().add(propertySpec);
+        propertyFilterSpec.getObjectSet().add(objectSpec);
+
+        List<PropertyFilterSpec> lstPfs = new ArrayList<>(1);
+
+        lstPfs.add(propertyFilterSpec);
+
+        VimPortType vimPortType = connection.getVimPortType();
+        ManagedObjectReference propertyCollector = connection.getServiceContent().getPropertyCollector();
+
+        List<ObjectContent> lstObjectContent = retrievePropertiesAllObjects(lstPfs, vimPortType, propertyCollector);
+
+        if (lstObjectContent != null) {
+            for (ObjectContent oc : lstObjectContent) {
+                ManagedObjectReference mor = oc.getObj();
+                List<DynamicProperty> dps = oc.getPropSet();
+                String vmName = null;
+
+                if (dps != null) {
+                    for (DynamicProperty dp : dps) {
+                        vmName = (String)dp.getVal();
+                    }
+                }
+
+                if (vmName != null) {
+                    nameToVm.put(vmName, mor);
+                }
+            }
+        }
+
+        return nameToVm;
+    }
+
+    public static Map<String, Object> getEntityProps(VMwareConnection connection, ManagedObjectReference entityMor, String[] props)
+            throws InvalidPropertyFaultMsg, RuntimeFaultFaultMsg {
+        Map<String, Object> retVal = new HashMap<>();
+
+        PropertySpec propertySpec = new PropertySpec();
+
+        propertySpec.setAll(Boolean.FALSE);
+        propertySpec.setType(entityMor.getType());
+        propertySpec.getPathSet().addAll(Arrays.asList(props));
+
+        ObjectSpec objectSpec = new ObjectSpec();
+
+        objectSpec.setObj(entityMor);
+
+        // Create PropertyFilterSpec using the PropertySpec and ObjectPec created above.
+        PropertyFilterSpec propertyFilterSpec = new PropertyFilterSpec();
+
+        propertyFilterSpec.getPropSet().add(propertySpec);
+        propertyFilterSpec.getObjectSet().add(objectSpec);
+
+        List<PropertyFilterSpec> propertyFilterSpecs = new ArrayList<>();
+
+        propertyFilterSpecs.add(propertyFilterSpec);
+
+        RetrieveResult rslts = connection.getVimPortType().retrievePropertiesEx(connection.getServiceContent().getPropertyCollector(),
+                propertyFilterSpecs, new RetrieveOptions());
+        List<ObjectContent> listobjcontent = new ArrayList<>();
+
+        if (rslts != null && rslts.getObjects() != null && !rslts.getObjects().isEmpty()) {
+            listobjcontent.addAll(rslts.getObjects());
+        }
+
+        String token = null;
+
+        if (rslts != null && rslts.getToken() != null) {
+            token = rslts.getToken();
+        }
+
+        while (token != null && !token.isEmpty()) {
+            rslts = connection.getVimPortType().continueRetrievePropertiesEx(connection.getServiceContent().getPropertyCollector(),
+                    token);
+
+            token = null;
+
+            if (rslts != null) {
+                token = rslts.getToken();
+
+                if (rslts.getObjects() != null && !rslts.getObjects().isEmpty()) {
+                    listobjcontent.addAll(rslts.getObjects());
+                }
+            }
+        }
+
+        for (ObjectContent oc : listobjcontent) {
+            List<DynamicProperty> dps = oc.getPropSet();
+
+            if (dps != null) {
+                for (DynamicProperty dp : dps) {
+                    retVal.put(dp.getName(), dp.getVal());
+                }
+            }
+        }
+
+        return retVal;
+    }
+
+    public static ManagedObjectReference reconfigureVm(VMwareConnection connection, ManagedObjectReference morVm,
+            VirtualMachineConfigSpec vmcs) throws Exception {
+        return connection.getVimPortType().reconfigVMTask(morVm, vmcs);
+    }
+
+    public static VirtualMachineDiskInfoBuilder getDiskInfoBuilder(List<VirtualDevice> devices) throws Exception {
+        VirtualMachineDiskInfoBuilder builder = new VirtualMachineDiskInfoBuilder();
+
+        if (devices != null) {
+            for (VirtualDevice device : devices) {
+                if (device instanceof VirtualDisk) {
+                    VirtualDisk virtualDisk = (VirtualDisk)device;
+                    VirtualDeviceBackingInfo backingInfo = virtualDisk.getBacking();
+
+                    if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
+                        VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo)backingInfo;
+
+                        String deviceBusName = VMwareUtil.getDeviceBusName(devices, virtualDisk);
+
+                        while (diskBackingInfo != null) {
+                            builder.addDisk(deviceBusName, diskBackingInfo.getFileName());
+
+                            diskBackingInfo = diskBackingInfo.getParent();
+                        }
+                    }
+                }
+            }
+        }
+
+        return builder;
+    }
+
+    public static String getDeviceBusName(List<VirtualDevice> allDevices, VirtualDisk disk) throws Exception {
+        for (VirtualDevice device : allDevices) {
+            if (device.getKey() == disk.getControllerKey()) {
+                if (device instanceof VirtualIDEController) {
+                    return String.format("ide%d:%d", ((VirtualIDEController)device).getBusNumber(), disk.getUnitNumber());
+                } else if (device instanceof VirtualSCSIController) {
+                    return String.format("scsi%d:%d", ((VirtualSCSIController)device).getBusNumber(), disk.getUnitNumber());
+                } else {
+                    throw new Exception("The device controller is not supported.");
+                }
+            }
+        }
+
+        throw new Exception("The device controller could not be located.");
+    }
+
+    public static boolean waitForTask(VMwareConnection connection, ManagedObjectReference task) throws Exception {
+        try {
+            Object[] result = waitForValues(connection, task, new String[] { "info.state", "info.error" }, new String[] { "state" },
+                    new Object[][] { new Object[] { TaskInfoState.SUCCESS, TaskInfoState.ERROR } });
+
+            if (result[0].equals(TaskInfoState.SUCCESS)) {
+                return true;
+            }
+
+            if (result[1] instanceof LocalizedMethodFault) {
+                throw new Exception(((LocalizedMethodFault)result[1]).getLocalizedMessage());
+            }
+        } catch (WebServiceException we) {
+            s_logger.debug("Cancelling vCenter task because the task failed with the following error: " + we.getLocalizedMessage());
+
+            connection.getVimPortType().cancelTask(task);
+
+            throw new Exception("The vCenter task failed due to the following error: " + we.getLocalizedMessage());
+        }
+
+        return false;
+    }
+
+    private static Object[] waitForValues(VMwareConnection connection, ManagedObjectReference morObj, String[] filterProps,
+            String[] endWaitProps, Object[][] expectedVals) throws InvalidPropertyFaultMsg, RuntimeFaultFaultMsg,
+            InvalidCollectorVersionFaultMsg {
+        String version = "";
+        Object[] endVals = new Object[endWaitProps.length];
+        Object[] filterVals = new Object[filterProps.length];
+
+        PropertyFilterSpec spec = new PropertyFilterSpec();
+
+        ObjectSpec oSpec = new ObjectSpec();
+
+        oSpec.setObj(morObj);
+        oSpec.setSkip(Boolean.FALSE);
+
+        spec.getObjectSet().add(oSpec);
+
+        PropertySpec pSpec = new PropertySpec();
+
+        pSpec.getPathSet().addAll(Arrays.asList(filterProps));
+        pSpec.setType(morObj.getType());
+
+        spec.getPropSet().add(pSpec);
+
+        ManagedObjectReference propertyCollector = connection.getServiceContent().getPropertyCollector();
+        ManagedObjectReference filterSpecRef = connection.getVimPortType().createFilter(propertyCollector, spec, true);
+
+        boolean reached = false;
+
+        UpdateSet updateSet;
+        List<PropertyFilterUpdate> lstPropertyFilterUpdates;
+        List<ObjectUpdate> lstObjectUpdates;
+        List<PropertyChange> lstPropertyChanges;
+
+        while (!reached) {
+            updateSet = connection.getVimPortType().waitForUpdates(propertyCollector, version);
+
+            if (updateSet == null || updateSet.getFilterSet() == null) {
+                continue;
+            }
+
+            version = updateSet.getVersion();
+
+            lstPropertyFilterUpdates = updateSet.getFilterSet();
+
+            for (PropertyFilterUpdate propertyFilterUpdate : lstPropertyFilterUpdates) {
+                lstObjectUpdates = propertyFilterUpdate.getObjectSet();
+
+                for (ObjectUpdate objUpdate : lstObjectUpdates) {
+                    if (objUpdate.getKind() == ObjectUpdateKind.MODIFY || objUpdate.getKind() == ObjectUpdateKind.ENTER ||
+                            objUpdate.getKind() == ObjectUpdateKind.LEAVE) {
+                        lstPropertyChanges = objUpdate.getChangeSet();
+
+                        for (PropertyChange propchg : lstPropertyChanges) {
+                            updateValues(endWaitProps, endVals, propchg);
+                            updateValues(filterProps, filterVals, propchg);
+                        }
+                    }
+                }
+            }
+
+            Object expectedValue;
+
+            // Check if the expected values have been reached and exit the loop if done.
+            // Also, exit the WaitForUpdates loop if this is the case.
+            for (int chgi = 0; chgi < endVals.length && !reached; chgi++) {
+                for (int vali = 0; vali < expectedVals[chgi].length && !reached; vali++) {
+                    expectedValue = expectedVals[chgi][vali];
+
+                    reached = expectedValue.equals(endVals[chgi]) || reached;
+                }
+            }
+        }
+
+        // Destroy the filter when we are done.
+        connection.getVimPortType().destroyPropertyFilter(filterSpecRef);
+
+        return filterVals;
+    }
+
+    private static void updateValues(String[] props, Object[] vals, PropertyChange propertyChange) {
+        for (int findi = 0; findi < props.length; findi++) {
+            if (propertyChange.getName().lastIndexOf(props[findi]) >= 0) {
+                if (propertyChange.getOp() == PropertyChangeOp.REMOVE) {
+                    vals[findi] = "";
+                } else {
+                    vals[findi] = propertyChange.getVal();
+                }
+            }
+        }
+    }
+
+    private static List<ObjectContent> retrievePropertiesAllObjects(List<PropertyFilterSpec> lstPfs,
+            VimPortType vimPortType, ManagedObjectReference propCollectorRef) throws Exception {
+        List<ObjectContent> lstObjectContent = new ArrayList<>();
+
+        RetrieveOptions retrieveOptions = new RetrieveOptions();
+
+        RetrieveResult rslts = vimPortType.retrievePropertiesEx(propCollectorRef, lstPfs, retrieveOptions);
+
+        if (rslts != null && rslts.getObjects() != null && rslts.getObjects().size() > 0) {
+            List<ObjectContent> lstOc = new ArrayList<>();
+
+            for (ObjectContent oc : rslts.getObjects()) {
+                lstOc.add(oc);
+            }
+
+            lstObjectContent.addAll(lstOc);
+        }
+
+        String token = null;
+
+        if (rslts != null && rslts.getToken() != null) {
+            token = rslts.getToken();
+        }
+
+        while (token != null && !token.isEmpty()) {
+            rslts = vimPortType.continueRetrievePropertiesEx(propCollectorRef, token);
+            token = null;
+
+            if (rslts != null) {
+                token = rslts.getToken();
+
+                if (rslts.getObjects() != null && rslts.getObjects().size() > 0) {
+                    List<ObjectContent> lstOc = new ArrayList<>();
+
+                    for (ObjectContent oc : rslts.getObjects()) {
+                        lstOc.add(oc);
+                    }
+
+                    lstObjectContent.addAll(lstOc);
+                }
+            }
+        }
+
+        return lstObjectContent;
+    }
+
+    private static TraversalSpec getVMTraversalSpec() {
+        // Create a TraversalSpec that starts from the 'root' objects
+        // and traverses the inventory tree to get to the VirtualMachines.
+        // Build the traversal specs bottoms up
+
+        // TraversalSpec to get to the VM in a vApp
+        TraversalSpec vAppToVM = new TraversalSpec();
+
+        vAppToVM.setName("vAppToVM");
+        vAppToVM.setType("VirtualApp");
+        vAppToVM.setPath("vm");
+
+        // TraversalSpec for vApp to vApp
+        TraversalSpec vAppToVApp = new TraversalSpec();
+
+        vAppToVApp.setName("vAppToVApp");
+        vAppToVApp.setType("VirtualApp");
+        vAppToVApp.setPath("resourcePool");
+
+        // SelectionSpec for vApp-to-vApp recursion
+        SelectionSpec vAppRecursion = new SelectionSpec();
+
+        vAppRecursion.setName("vAppToVApp");
+
+        // SelectionSpec to get to a VM in the vApp
+        SelectionSpec vmInVApp = new SelectionSpec();
+
+        vmInVApp.setName("vAppToVM");
+
+        // SelectionSpec for both vApp to vApp and vApp to VM
+        List<SelectionSpec> vAppToVMSS = new ArrayList<>();
+
+        vAppToVMSS.add(vAppRecursion);
+        vAppToVMSS.add(vmInVApp);
+
+        vAppToVApp.getSelectSet().addAll(vAppToVMSS);
+
+        // This SelectionSpec is used for recursion for Folder recursion
+        SelectionSpec sSpec = new SelectionSpec();
+
+        sSpec.setName("VisitFolders");
+
+        // Traversal to get to the vmFolder from DataCenter
+        TraversalSpec dataCenterToVMFolder = new TraversalSpec();
+
+        dataCenterToVMFolder.setName("DataCenterToVMFolder");
+        dataCenterToVMFolder.setType("Datacenter");
+        dataCenterToVMFolder.setPath("vmFolder");
+        dataCenterToVMFolder.setSkip(false);
+
+        dataCenterToVMFolder.getSelectSet().add(sSpec);
+
+        // TraversalSpec to get to the DataCenter from rootFolder
+        TraversalSpec traversalSpec = new TraversalSpec();
+
+        traversalSpec.setName("VisitFolders");
+        traversalSpec.setType("Folder");
+        traversalSpec.setPath("childEntity");
+        traversalSpec.setSkip(false);
+
+        List<SelectionSpec> sSpecArr = new ArrayList<>();
+
+        sSpecArr.add(sSpec);
+        sSpecArr.add(dataCenterToVMFolder);
+        sSpecArr.add(vAppToVM);
+        sSpecArr.add(vAppToVApp);
+
+        traversalSpec.getSelectSet().addAll(sSpecArr);
+
+        return traversalSpec;
+    }
+
+    private static void trustAllHttpsCertificates() throws Exception {
+        // Create a trust manager that does not validate certificate chains:
+        TrustManager[] trustAllCerts = new TrustManager[1];
+
+        TrustManager tm = new TrustAllTrustManager();
+
+        trustAllCerts[0] = tm;
+
+        SSLContext sc = SSLContext.getInstance("SSL");
+
+        SSLSessionContext sslsc = sc.getServerSessionContext();
+
+        sslsc.setSessionTimeout(0);
+
+        sc.init(null, trustAllCerts, null);
+
+        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+     }
+
+    private static class TrustAllTrustManager implements TrustManager, X509TrustManager {
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index 6428584..9b7fb2e 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -2367,6 +2367,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
         vm.getDevices().addDevice(getVifDriver(nic.getType(), nic.getName()).plug(nic, vm.getPlatformEmulator(), nicAdapter));
     }
 
+    public boolean cleanupDisk(Map<String, String> volumeToDisconnect) {
+        return _storagePoolMgr.disconnectPhysicalDisk(volumeToDisconnect);
+    }
+
     public boolean cleanupDisk(final DiskDef disk) {
         final String path = disk.getDiskPath();
 
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java
index 2df6c65..4b2afa6 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java
@@ -33,24 +33,39 @@ public class MigrateKVMAsync implements Callable<Domain> {
     private String dxml = "";
     private String vmName = "";
     private String destIp = "";
+    private boolean migrateStorage;
+    private boolean autoConvergence;
 
-    public MigrateKVMAsync(final LibvirtComputingResource libvirtComputingResource, final Domain dm, final Connect dconn, final String dxml, final String vmName, final String destIp) {
+    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) {
         this.libvirtComputingResource = libvirtComputingResource;
 
         this.dm = dm;
         this.dconn = dconn;
         this.dxml = dxml;
+        this.migrateStorage = migrateStorage;
+        this.autoConvergence = autoConvergence;
         this.vmName = vmName;
         this.destIp = destIp;
     }
 
     @Override
     public Domain call() throws LibvirtException {
-        // set compression flag for migration if libvirt version supports it
-        if (dconn.getLibVirVersion() < 1003000) {
-            return dm.migrate(dconn, 1 << 0, dxml, vmName, "tcp:" + destIp, libvirtComputingResource.getMigrateSpeed());
-        } else {
-            return dm.migrate(dconn, 1 << 0|1 << 11, dxml, vmName, "tcp:" + destIp, libvirtComputingResource.getMigrateSpeed());
+        long flags = 1 << 0;
+
+        // set compression flag for migration, if libvirt version supports it
+        if (dconn.getLibVirVersion() >= 1000003) {
+            flags |= 1 << 11;
+        }
+
+        if (migrateStorage) {
+            flags |= 1 << 6;
         }
+
+        if (autoConvergence && dconn.getLibVirVersion() >= 1002003) {
+            flags |= 1 << 13;
+        }
+
+        return dm.migrate(dconn, flags, dxml, vmName, "tcp:" + destIp, libvirtComputingResource.getMigrateSpeed());
     }
 }
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyVolumeCommandWrapper.java
index b2248b9..0795abf 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyVolumeCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyVolumeCommandWrapper.java
@@ -20,10 +20,12 @@
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
 import java.io.File;
+import java.util.Map;
 
 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.to.DiskTO;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
@@ -33,8 +35,13 @@ import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.utils.exception.CloudRuntimeException;
 
+import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.log4j.Logger;
+
 @ResourceWrapper(handles =  CopyVolumeCommand.class)
 public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVolumeCommand, Answer, LibvirtComputingResource> {
+    private static final Logger LOGGER = Logger.getLogger(LibvirtCopyVolumeCommandWrapper.class);
 
     @Override
     public Answer execute(final CopyVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
@@ -46,20 +53,30 @@ public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVo
          * ManagementServerImpl shows that it always sets copyToSecondary to
          * true
          */
+
+        Map<String, String> srcDetails = command.getSrcDetails();
+
+        if (srcDetails != null) {
+            return handleCopyDataFromVolumeToSecondaryStorageUsingSrcDetails(command, libvirtComputingResource);
+        }
+
+        final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
+
         final boolean copyToSecondary = command.toSecondaryStorage();
-        String volumePath = command.getVolumePath();
         final StorageFilerTO pool = command.getPool();
         final String secondaryStorageUrl = command.getSecondaryStorageURL();
+
         KVMStoragePool secondaryStoragePool = null;
-        KVMStoragePool primaryPool = null;
+        String volumePath;
+        KVMStoragePool primaryPool;
 
-        final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
         try {
             try {
                 primaryPool = storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid());
             } catch (final CloudRuntimeException e) {
                 if (e.getMessage().contains("not found")) {
-                    primaryPool = storagePoolMgr.createStoragePool(pool.getUuid(), pool.getHost(), pool.getPort(), pool.getPath(), pool.getUserInfo(), pool.getType());
+                    primaryPool = storagePoolMgr.createStoragePool(pool.getUuid(), pool.getHost(), pool.getPort(), pool.getPath(),
+                            pool.getUserInfo(), pool.getType());
                 } else {
                     return new CopyVolumeAnswer(command, false, e.getMessage(), null, null);
                 }
@@ -85,6 +102,7 @@ public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVo
                 secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl + volumePath);
 
                 final KVMPhysicalDisk volume = secondaryStoragePool.getPhysicalDisk(command.getVolumePath() + ".qcow2");
+
                 storagePoolMgr.copyPhysicalDisk(volume, volumeName, primaryPool, 0);
 
                 return new CopyVolumeAnswer(command, true, null, null, volumeName);
@@ -97,4 +115,61 @@ public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVo
             }
         }
     }
-}
\ No newline at end of file
+
+    private Answer handleCopyDataFromVolumeToSecondaryStorageUsingSrcDetails(CopyVolumeCommand command, LibvirtComputingResource libvirtComputingResource) {
+        KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
+        PrimaryDataStoreTO srcPrimaryDataStore = null;
+        KVMStoragePool secondaryStoragePool = null;
+
+        Map<String, String> srcDetails = command.getSrcDetails();
+
+        String srcPath = srcDetails.get(DiskTO.IQN);
+
+        if (srcPath == null) {
+            return new CopyVolumeAnswer(command, false, "No IQN was specified", null, null);
+        }
+
+        try {
+            LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
+            String destVolumeName = libvirtUtilitiesHelper.generateUUIDName() + ".qcow2";
+            String destVolumePath = command.getVolumePath() + File.separator;
+
+            String secondaryStorageUrl = command.getSecondaryStorageURL();
+
+            secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl);
+
+            secondaryStoragePool.createFolder(File.separator + destVolumePath);
+
+            storagePoolMgr.deleteStoragePool(secondaryStoragePool.getType(), secondaryStoragePool.getUuid());
+
+            secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl + File.separator + destVolumePath);
+
+            VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData();
+
+            srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore();
+
+            storagePoolMgr.connectPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath, srcDetails);
+
+            KVMPhysicalDisk srcPhysicalDisk = storagePoolMgr.getPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath);
+
+            storagePoolMgr.copyPhysicalDisk(srcPhysicalDisk, destVolumeName, secondaryStoragePool, command.getWait() * 1000);
+
+            return new CopyVolumeAnswer(command, true, null, null, destVolumePath + destVolumeName);
+        } catch (final CloudRuntimeException e) {
+            return new CopyVolumeAnswer(command, false, e.toString(), null, null);
+        } finally {
+            try {
+                if (srcPrimaryDataStore != null) {
+                    storagePoolMgr.disconnectPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath);
+                }
+            }
+            catch (Exception e) {
+                LOGGER.warn("Unable to disconnect from the source device.", e);
+            }
+
+            if (secondaryStoragePool != null) {
+                storagePoolMgr.deleteStoragePool(secondaryStoragePool.getType(), secondaryStoragePool.getUuid());
+            }
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteStoragePoolCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteStoragePoolCommandWrapper.java
index 12ba874c..08e414a 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteStoragePoolCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteStoragePoolCommandWrapper.java
@@ -30,13 +30,17 @@ import com.cloud.utils.exception.CloudRuntimeException;
 
 @ResourceWrapper(handles =  DeleteStoragePoolCommand.class)
 public final class LibvirtDeleteStoragePoolCommandWrapper extends CommandWrapper<DeleteStoragePoolCommand, Answer, LibvirtComputingResource> {
-
     @Override
     public Answer execute(final DeleteStoragePoolCommand command, final LibvirtComputingResource libvirtComputingResource) {
         try {
-            final StorageFilerTO pool = command.getPool();
-            final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
-            storagePoolMgr.deleteStoragePool(pool.getType(), pool.getUuid());
+            // if getRemoveDatastore() is true, then we are dealing with managed storage and can skip the delete logic here
+            if (!command.getRemoveDatastore()) {
+                final StorageFilerTO pool = command.getPool();
+                final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
+
+                storagePoolMgr.deleteStoragePool(pool.getType(), pool.getUuid());
+            }
+
             return new Answer(command);
         } catch (final CloudRuntimeException e) {
             return new Answer(command, false, e.toString());
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
index 9e7b78e..6ed56fb 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
@@ -19,7 +19,12 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -28,12 +33,32 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.io.IOUtils;
 import org.apache.log4j.Logger;
+
 import org.libvirt.Connect;
 import org.libvirt.Domain;
 import org.libvirt.DomainInfo.DomainState;
 import org.libvirt.LibvirtException;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.MigrateAnswer;
 import com.cloud.agent.api.MigrateCommand;
@@ -45,6 +70,7 @@ import com.cloud.hypervisor.kvm.resource.VifDriver;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.utils.Ternary;
+import com.cloud.utils.exception.CloudRuntimeException;
 
 @ResourceWrapper(handles =  MigrateCommand.class)
 public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, LibvirtComputingResource> {
@@ -61,7 +87,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
         String result = null;
 
         List<InterfaceDef> ifaces = null;
-        List<DiskDef> disks = null;
+        List<DiskDef> disks;
 
         Domain dm = null;
         Connect dconn = null;
@@ -69,6 +95,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
         Connect conn = null;
         String xmlDesc = null;
         List<Ternary<String, Boolean, String>> vmsnapshots = null;
+
         try {
             final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
 
@@ -79,7 +106,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
             /*
                 We replace the private IP address with the address of the destination host.
                 This is because the VNC listens on the private IP address of the hypervisor,
-                but that address is ofcourse different on the target host.
+                but that address is of course different on the target host.
 
                 MigrateCommand.getDestinationIp() returns the private IP address of the target
                 hypervisor. So it's safe to use.
@@ -104,12 +131,19 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
             // delete the metadata of vm snapshots before migration
             vmsnapshots = libvirtComputingResource.cleanVMSnapshotMetadata(dm);
 
+            Map<String, MigrateCommand.MigrateDiskInfo> mapMigrateStorage = command.getMigrateStorage();
+
+            if (MapUtils.isNotEmpty(mapMigrateStorage)) {
+                xmlDesc = replaceStorage(xmlDesc, mapMigrateStorage);
+            }
+
             dconn = libvirtUtilitiesHelper.retrieveQemuConnection("qemu+tcp://" + command.getDestinationIp() + "/system");
 
             //run migration in thread so we can monitor it
             s_logger.info("Live migration of instance " + vmName + " initiated");
             final ExecutorService executor = Executors.newFixedThreadPool(1);
-            final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc, vmName, command.getDestinationIp());
+            final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc, MapUtils.isNotEmpty(mapMigrateStorage),
+                    command.isAutoConvergence(), vmName, command.getDestinationIp());
             final Future<Domain> migrateThread = executor.submit(worker);
             executor.shutdown();
             long sleeptime = 0;
@@ -167,6 +201,21 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
         } catch (final TimeoutException e) {
             s_logger.debug("Timed out while migrating domain: " + e.getMessage());
             result = e.getMessage();
+        } catch (final IOException e) {
+            s_logger.debug("IOException: " + e.getMessage());
+            result = e.getMessage();
+        } catch (final ParserConfigurationException e) {
+            s_logger.debug("ParserConfigurationException: " + e.getMessage());
+            result = e.getMessage();
+        } catch (final SAXException e) {
+            s_logger.debug("SAXException: " + e.getMessage());
+            result = e.getMessage();
+        } catch (final TransformerConfigurationException e) {
+            s_logger.debug("TransformerConfigurationException: " + e.getMessage());
+            result = e.getMessage();
+        } catch (final TransformerException e) {
+            s_logger.debug("TransformerException: " + e.getMessage());
+            result = e.getMessage();
         } finally {
             try {
                 if (dm != null && result != null) {
@@ -230,4 +279,138 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
         }
         return xmlDesc;
     }
+
+    // Pass in a list of the disks to update in the XML (xmlDesc). Each disk passed in needs to have a serial number. If any disk's serial number in the
+    // list does not match a disk in the XML, an exception should be thrown.
+    // In addition to the serial number, each disk in the list needs the following info:
+    //   * The value of the 'type' of the disk (ex. file, block)
+    //   * The value of the 'type' of the driver of the disk (ex. qcow2, raw)
+    //   * The source of the disk needs an attribute that is either 'file' or 'dev' as well as its corresponding value.
+    private String replaceStorage(String xmlDesc, Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage)
+            throws IOException, ParserConfigurationException, SAXException, TransformerException {
+        InputStream in = IOUtils.toInputStream(xmlDesc);
+
+        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+        Document doc = docBuilder.parse(in);
+
+        // Get the root element
+        Node domainNode = doc.getFirstChild();
+
+        NodeList domainChildNodes = domainNode.getChildNodes();
+
+        for (int i = 0; i < domainChildNodes.getLength(); i++) {
+            Node domainChildNode = domainChildNodes.item(i);
+
+            if ("devices".equals(domainChildNode.getNodeName())) {
+                NodeList devicesChildNodes = domainChildNode.getChildNodes();
+
+                for (int x = 0; x < devicesChildNodes.getLength(); x++) {
+                    Node deviceChildNode = devicesChildNodes.item(x);
+
+                    if ("disk".equals(deviceChildNode.getNodeName())) {
+                        Node diskNode = deviceChildNode;
+
+                        String sourceFileDevText = getSourceFileDevText(diskNode);
+
+                        String path = getPathFromSourceFileDevText(migrateStorage.keySet(), sourceFileDevText);
+
+                        if (path != null) {
+                            MigrateCommand.MigrateDiskInfo migrateDiskInfo = migrateStorage.remove(path);
+
+                            NamedNodeMap diskNodeAttributes = diskNode.getAttributes();
+                            Node diskNodeAttribute = diskNodeAttributes.getNamedItem("type");
+
+                            diskNodeAttribute.setTextContent(migrateDiskInfo.getDiskType().toString());
+
+                            NodeList diskChildNodes = diskNode.getChildNodes();
+
+                            for (int z = 0; z < diskChildNodes.getLength(); z++) {
+                                Node diskChildNode = diskChildNodes.item(z);
+
+                                if ("driver".equals(diskChildNode.getNodeName())) {
+                                    Node driverNode = diskChildNode;
+
+                                    NamedNodeMap driverNodeAttributes = driverNode.getAttributes();
+                                    Node driverNodeAttribute = driverNodeAttributes.getNamedItem("type");
+
+                                    driverNodeAttribute.setTextContent(migrateDiskInfo.getDriverType().toString());
+                                } else if ("source".equals(diskChildNode.getNodeName())) {
+                                    diskNode.removeChild(diskChildNode);
+
+                                    Element newChildSourceNode = doc.createElement("source");
+
+                                    newChildSourceNode.setAttribute(migrateDiskInfo.getSource().toString(), migrateDiskInfo.getSourceText());
+
+                                    diskNode.appendChild(newChildSourceNode);
+                                } else if ("auth".equals(diskChildNode.getNodeName())) {
+                                    diskNode.removeChild(diskChildNode);
+                                } else if ("iotune".equals(diskChildNode.getNodeName())) {
+                                    diskNode.removeChild(diskChildNode);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!migrateStorage.isEmpty()) {
+            throw new CloudRuntimeException("Disk info was passed into LibvirtMigrateCommandWrapper.replaceStorage that was not used.");
+        }
+
+        return getXml(doc);
+    }
+
+    private String getPathFromSourceFileDevText(Set<String> paths, String sourceFileDevText) {
+        if (paths != null && sourceFileDevText != null) {
+            for (String path : paths) {
+                if (sourceFileDevText.contains(path)) {
+                    return path;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private String getSourceFileDevText(Node diskNode) {
+        NodeList diskChildNodes = diskNode.getChildNodes();
+
+        for (int i = 0; i < diskChildNodes.getLength(); i++) {
+            Node diskChildNode = diskChildNodes.item(i);
+
+            if ("source".equals(diskChildNode.getNodeName())) {
+                NamedNodeMap diskNodeAttributes = diskChildNode.getAttributes();
+
+                Node diskNodeAttribute = diskNodeAttributes.getNamedItem("file");
+
+                if (diskNodeAttribute != null) {
+                    return diskNodeAttribute.getTextContent();
+                }
+
+                diskNodeAttribute = diskNodeAttributes.getNamedItem("dev");
+
+                if (diskNodeAttribute != null) {
+                    return diskNodeAttribute.getTextContent();
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private String getXml(Document doc) throws TransformerException {
+        TransformerFactory transformerFactory = TransformerFactory.newInstance();
+        Transformer transformer = transformerFactory.newTransformer();
+
+        DOMSource source = new DOMSource(doc);
+
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        StreamResult result = new StreamResult(byteArrayOutputStream);
+
+        transformer.transform(source, result);
+
+        return byteArrayOutputStream.toString();
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java
new file mode 100644
index 0000000..311eb67
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java
@@ -0,0 +1,95 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.storage.MigrateVolumeAnswer;
+import com.cloud.agent.api.storage.MigrateVolumeCommand;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+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 java.util.Map;
+import java.util.UUID;
+
+import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.log4j.Logger;
+
+@ResourceWrapper(handles =  MigrateVolumeCommand.class)
+public final class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVolumeCommand, Answer, LibvirtComputingResource> {
+    private static final Logger LOGGER = Logger.getLogger(LibvirtMigrateVolumeCommandWrapper.class);
+
+    @Override
+    public Answer execute(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
+        KVMStoragePoolManager storagePoolManager = libvirtComputingResource.getStoragePoolMgr();
+
+        VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData();
+        PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore();
+
+        Map<String, String> srcDetails = command.getSrcDetails();
+
+        String srcPath = srcDetails != null ? srcDetails.get(DiskTO.IQN) : srcVolumeObjectTO.getPath();
+
+        VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData();
+        PrimaryDataStoreTO destPrimaryDataStore = (PrimaryDataStoreTO)destVolumeObjectTO.getDataStore();
+
+        Map<String, String> destDetails = command.getDestDetails();
+
+        String destPath = destDetails != null && destDetails.get(DiskTO.IQN) != null ? destDetails.get(DiskTO.IQN) :
+                (destVolumeObjectTO.getPath() != null ? destVolumeObjectTO.getPath() : UUID.randomUUID().toString());
+
+        try {
+            storagePoolManager.connectPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath, srcDetails);
+
+            KVMPhysicalDisk srcPhysicalDisk = storagePoolManager.getPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath);
+
+            KVMStoragePool destPrimaryStorage = storagePoolManager.getStoragePool(destPrimaryDataStore.getPoolType(), destPrimaryDataStore.getUuid());
+
+            storagePoolManager.connectPhysicalDisk(destPrimaryDataStore.getPoolType(), destPrimaryDataStore.getUuid(), destPath, destDetails);
+
+            storagePoolManager.copyPhysicalDisk(srcPhysicalDisk, destPath, destPrimaryStorage, command.getWaitInMillSeconds());
+        }
+        catch (Exception ex) {
+            return new MigrateVolumeAnswer(command, false, ex.getMessage(), null);
+        }
+        finally {
+            try {
+                storagePoolManager.disconnectPhysicalDisk(destPrimaryDataStore.getPoolType(), destPrimaryDataStore.getUuid(), destPath);
+            }
+            catch (Exception e) {
+                LOGGER.warn("Unable to disconnect from the destination device.", e);
+            }
+
+            try {
+                storagePoolManager.disconnectPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath);
+            }
+            catch (Exception e) {
+                LOGGER.warn("Unable to disconnect from the source device.", e);
+            }
+        }
+
+        return new MigrateVolumeAnswer(command, true, null, destPath);
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyTargetsCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyTargetsCommandWrapper.java
new file mode 100644
index 0000000..627d4b7
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyTargetsCommandWrapper.java
@@ -0,0 +1,80 @@
+//
+// 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 java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.ModifyTargetsAnswer;
+import com.cloud.agent.api.ModifyTargetsCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@ResourceWrapper(handles =  ModifyTargetsCommand.class)
+public final class LibvirtModifyTargetsCommandWrapper extends CommandWrapper<ModifyTargetsCommand, Answer, LibvirtComputingResource> {
+    private static final Logger s_logger = Logger.getLogger(LibvirtMigrateCommandWrapper.class);
+
+    @Override
+    public Answer execute(final ModifyTargetsCommand command, final LibvirtComputingResource libvirtComputingResource) {
+        KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
+
+        List<Map<String, String>> targets = command.getTargets();
+
+        // When attempting to connect to one or more targets, place the successfully connected path into this List.
+        List<String> connectedPaths = new ArrayList<>(targets.size());
+
+        for (Map<String, String> target : targets) {
+            StoragePoolType storagePoolType = StoragePoolType.valueOf(target.get(ModifyTargetsCommand.STORAGE_TYPE));
+            String storageUuid = target.get(ModifyTargetsCommand.STORAGE_UUID);
+            String path = target.get(ModifyTargetsCommand.IQN);
+
+            if (command.getAdd()) {
+                if (storagePoolMgr.connectPhysicalDisk(storagePoolType, storageUuid, path, target)) {
+                    KVMPhysicalDisk kvmPhysicalDisk = storagePoolMgr.getPhysicalDisk(storagePoolType, storageUuid, path);
+
+                    connectedPaths.add(kvmPhysicalDisk.getPath());
+                }
+                else {
+                    throw new CloudRuntimeException("Unable to connect to the following target: " + path);
+                }
+            }
+            else {
+                if (!storagePoolMgr.disconnectPhysicalDisk(storagePoolType, storageUuid, path)) {
+                    throw new CloudRuntimeException("Unable to disconnect from the following target: " + path);
+                }
+            }
+        }
+
+        ModifyTargetsAnswer modifyTargetsAnswer =  new ModifyTargetsAnswer();
+
+        modifyTargetsAnswer.setConnectedPaths(connectedPaths);
+
+        return modifyTargetsAnswer;
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
index 940a0a7..ac9f884 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
@@ -45,6 +45,11 @@ public final class LibvirtPrepareForMigrationCommandWrapper extends CommandWrapp
     @Override
     public Answer execute(final PrepareForMigrationCommand command, final LibvirtComputingResource libvirtComputingResource) {
         final VirtualMachineTO vm = command.getVirtualMachine();
+
+        if (command.isRollback()) {
+            return handleRollback(command, libvirtComputingResource);
+        }
+
         if (s_logger.isDebugEnabled()) {
             s_logger.debug("Preparing host for migrating " + vm);
         }
@@ -89,4 +94,15 @@ public final class LibvirtPrepareForMigrationCommandWrapper extends CommandWrapp
             }
         }
     }
-}
\ No newline at end of file
+
+    private Answer handleRollback(PrepareForMigrationCommand command, LibvirtComputingResource libvirtComputingResource) {
+        KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
+        VirtualMachineTO vmTO = command.getVirtualMachine();
+
+        if (!storagePoolMgr.disconnectPhysicalDisksViaVmSpec(vmTO)) {
+            return new PrepareForMigrationAnswer(command, "failed to disconnect physical disks from host");
+        }
+
+        return new PrepareForMigrationAnswer(command);
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
index 7e4ee22..bb837b5 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
@@ -19,8 +19,9 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
-import java.util.List;
 import java.io.File;
+import java.util.List;
+import java.util.Map;
 
 import com.cloud.utils.Pair;
 import com.cloud.utils.ssh.SshHelper;
@@ -88,9 +89,23 @@ public final class LibvirtStopCommandWrapper extends CommandWrapper<StopCommand,
             final String result = libvirtComputingResource.stopVM(conn, vmName, command.isForceStop());
 
             if (result == null) {
-                for (final DiskDef disk : disks) {
-                    libvirtComputingResource.cleanupDisk(disk);
+                if (disks != null && disks.size() > 0) {
+                    for (final DiskDef disk : disks) {
+                        libvirtComputingResource.cleanupDisk(disk);
+                    }
                 }
+                else {
+                    // When using iSCSI-based managed storage, if the user shuts a VM down from the guest OS (as opposed to doing so from CloudStack),
+                    // info needs to be passed to the KVM agent to have it disconnect KVM from the applicable iSCSI volumes.
+                    List<Map<String, String>> volumesToDisconnect = command.getVolumesToDisconnect();
+
+                    if (volumesToDisconnect != null) {
+                        for (Map<String, String> volumeToDisconnect : volumesToDisconnect) {
+                            libvirtComputingResource.cleanupDisk(volumeToDisconnect);
+                        }
+                    }
+                }
+
                 for (final InterfaceDef iface : ifaces) {
                     // We don't know which "traffic type" is associated with
                     // each interface at this point, so inform all vif drivers
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java
index 46a48c9..a90c97f 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java
@@ -20,12 +20,15 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import com.cloud.storage.Storage;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
 
 import com.cloud.agent.api.to.DiskTO;
+import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.ProvisioningType;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.utils.StringUtils;
@@ -37,7 +40,7 @@ import com.cloud.utils.script.Script;
 public class IscsiAdmStorageAdaptor implements StorageAdaptor {
     private static final Logger s_logger = Logger.getLogger(IscsiAdmStorageAdaptor.class);
 
-    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<String, KVMStoragePool>();
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
 
     @Override
     public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, StoragePoolType storagePoolType) {
@@ -115,7 +118,7 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
             }
         }
 
-        // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10 --login
+        // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --login
         iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
 
         iScsiAdmCmd.add("-m", "node");
@@ -165,6 +168,23 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
         }
     }
 
+    private void waitForDiskToBecomeUnavailable(String host, int port, String iqn, String lun) {
+        int numberOfTries = 10;
+        int timeBetweenTries = 1000;
+
+        String deviceByPath = getByPath(host, port, "/" + iqn + "/" + lun);
+
+        while (getDeviceSize(deviceByPath) > 0 && numberOfTries > 0) {
+            numberOfTries--;
+
+            try {
+                Thread.sleep(timeBetweenTries);
+            } catch (Exception ex) {
+                // don't do anything
+            }
+        }
+    }
+
     private void executeChapCommand(String path, KVMStoragePool pool, String nParameter, String vParameter, String detail) throws Exception {
         Script iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
 
@@ -193,13 +213,13 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
     }
 
     // example by-path: /dev/disk/by-path/ip-192.168.233.10:3260-iscsi-iqn.2012-03.com.solidfire:storagepool2-lun-0
-    private String getByPath(String host, String path) {
-        return "/dev/disk/by-path/ip-" + host + "-iscsi-" + getIqn(path) + "-lun-" + getLun(path);
+    private static String getByPath(String host, int port, String path) {
+        return "/dev/disk/by-path/ip-" + host + ":" + port + "-iscsi-" + getIqn(path) + "-lun-" + getLun(path);
     }
 
     @Override
     public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
-        String deviceByPath = getByPath(pool.getSourceHost() + ":" + pool.getSourcePort(), volumeUuid);
+        String deviceByPath = getByPath(pool.getSourceHost(), pool.getSourcePort(), volumeUuid);
         KVMPhysicalDisk physicalDisk = new KVMPhysicalDisk(deviceByPath, volumeUuid, pool);
 
         physicalDisk.setFormat(PhysicalDiskFormat.RAW);
@@ -226,6 +246,9 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
 
             return 0;
         }
+        else {
+            s_logger.info("Successfully retrieved the size of device " + deviceByPath);
+        }
 
         return Long.parseLong(parser.getLine());
     }
@@ -252,10 +275,10 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
         return tmp[index].trim();
     }
 
-    public boolean disconnectPhysicalDisk(String host, int port, String iqn, String lun) {
+    private boolean disconnectPhysicalDisk(String host, int port, String iqn, String lun) {
         // use iscsiadm to log out of the iSCSI target and un-discover it
 
-        // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10 --logout
+        // ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --logout
         Script iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
 
         iScsiAdmCmd.add("-m", "node");
@@ -295,6 +318,8 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
             System.out.println("Removed iSCSI target /" + iqn + "/" + lun);
         }
 
+        waitForDiskToBecomeUnavailable(host, port, iqn, lun);
+
         return true;
     }
 
@@ -304,13 +329,26 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
+        String host = volumeToDisconnect.get(DiskTO.STORAGE_HOST);
+        String port = volumeToDisconnect.get(DiskTO.STORAGE_PORT);
+        String path = volumeToDisconnect.get(DiskTO.IQN);
+
+        if (host != null && port != null && path != null) {
+            return disconnectPhysicalDisk(host, Integer.parseInt(port), getIqn(path), getLun(path));
+        }
+
+        return false;
+    }
+
+    @Override
     public boolean disconnectPhysicalDiskByPath(String localPath) {
         String search1 = "/dev/disk/by-path/ip-";
         String search2 = ":";
         String search3 = "-iscsi-";
         String search4 = "-lun-";
 
-        if (localPath.indexOf(search3) == -1) {
+        if (!localPath.contains(search3)) {
             // this volume doesn't below to this adaptor, so just return true
             return true;
         }
@@ -356,8 +394,37 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
-    public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
-        throw new UnsupportedOperationException("Copying a disk is not supported in this configuration.");
+    public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk srcDisk, String destVolumeUuid, KVMStoragePool destPool, int timeout) {
+        QemuImg q = new QemuImg(timeout);
+
+        QemuImgFile srcFile;
+
+        KVMStoragePool srcPool = srcDisk.getPool();
+
+        if (srcPool.getType() == StoragePoolType.RBD) {
+            srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(srcPool.getSourceHost(), srcPool.getSourcePort(),
+                                                                       srcPool.getAuthUserName(), srcPool.getAuthSecret(),
+                                                                       srcDisk.getPath()),srcDisk.getFormat());
+        } else {
+            srcFile = new QemuImgFile(srcDisk.getPath(), srcDisk.getFormat());
+        }
+
+        KVMPhysicalDisk destDisk = destPool.getPhysicalDisk(destVolumeUuid);
+
+        QemuImgFile destFile = new QemuImgFile(destDisk.getPath(), destDisk.getFormat());
+
+        try {
+            q.convert(srcFile, destFile);
+        } catch (QemuImgException ex) {
+            String msg = "Failed to copy data from " + srcDisk.getPath() + " to " +
+                    destDisk.getPath() + ". The error was the following: " + ex.getMessage();
+
+            s_logger.error(msg);
+
+            throw new CloudRuntimeException(msg);
+        }
+
+        return destPool.getPhysicalDisk(destVolumeUuid);
     }
 
     @Override
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
index 28e5f03..c613508 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
@@ -158,6 +158,18 @@ public class KVMStoragePoolManager {
         return result;
     }
 
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
+        for (Map.Entry<String, StorageAdaptor> set : _storageMapper.entrySet()) {
+            StorageAdaptor adaptor = set.getValue();
+
+            if (adaptor.disconnectPhysicalDisk(volumeToDisconnect)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     public boolean disconnectPhysicalDiskByPath(String path) {
         for (Map.Entry<String, StorageAdaptor> set : _storageMapper.entrySet()) {
             StorageAdaptor adaptor = set.getValue();
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index 72e0ca2..f09e8f7 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -237,8 +237,17 @@ public class KVMStorageProcessor implements StorageProcessor {
                 }
                 primaryVol = storagePoolMgr.copyPhysicalDisk(tmplVol, volume.getUuid(), primaryPool, cmd.getWaitInMillSeconds());
             } else if (destData instanceof TemplateObjectTO) {
-                final TemplateObjectTO destTempl = (TemplateObjectTO)destData;
-                primaryVol = storagePoolMgr.copyPhysicalDisk(tmplVol, destTempl.getUuid(), primaryPool, cmd.getWaitInMillSeconds());
+                TemplateObjectTO destTempl = (TemplateObjectTO)destData;
+
+                Map<String, String> details = primaryStore.getDetails();
+
+                String path = details != null ? details.get("managedStoreTarget") : null;
+
+                storagePoolMgr.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path, details);
+
+                primaryVol = storagePoolMgr.copyPhysicalDisk(tmplVol, path != null ? path : destTempl.getUuid(), primaryPool, cmd.getWaitInMillSeconds());
+
+                storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
             } else {
                 primaryVol = storagePoolMgr.copyPhysicalDisk(tmplVol, UUID.randomUUID().toString(), primaryPool, cmd.getWaitInMillSeconds());
             }
@@ -422,24 +431,41 @@ public class KVMStorageProcessor implements StorageProcessor {
                 }
             }
 
+            Map<String, String> details = cmd.getOptions2();
+
+            String path = details != null ? details.get(DiskTO.IQN) : null;
+
+            storagePoolMgr.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path, details);
+
             final String volumeName = UUID.randomUUID().toString();
 
             final int index = srcVolumePath.lastIndexOf(File.separator);
             final String volumeDir = srcVolumePath.substring(0, index);
             String srcVolumeName = srcVolumePath.substring(index + 1);
+
             secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl + File.separator + volumeDir);
+
             if (!srcVolumeName.endsWith(".qcow2") && srcFormat == ImageFormat.QCOW2) {
                 srcVolumeName = srcVolumeName + ".qcow2";
             }
+
             final KVMPhysicalDisk volume = secondaryStoragePool.getPhysicalDisk(srcVolumeName);
+
             volume.setFormat(PhysicalDiskFormat.valueOf(srcFormat.toString()));
-            final KVMPhysicalDisk newDisk = storagePoolMgr.copyPhysicalDisk(volume, volumeName, primaryPool, cmd.getWaitInMillSeconds());
+
+            final KVMPhysicalDisk newDisk = storagePoolMgr.copyPhysicalDisk(volume, path != null ? path : volumeName, primaryPool, cmd.getWaitInMillSeconds());
+
+            storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
+
             final VolumeObjectTO newVol = new VolumeObjectTO();
+
             newVol.setFormat(ImageFormat.valueOf(newDisk.getFormat().toString().toUpperCase()));
-            newVol.setPath(volumeName);
+            newVol.setPath(path != null ? path : volumeName);
+
             return new CopyCmdAnswer(newVol);
         } catch (final CloudRuntimeException e) {
-            s_logger.debug("Failed to ccopyVolumeFromImageCacheToPrimary: ", e);
+            s_logger.debug("Failed to copyVolumeFromImageCacheToPrimary: ", e);
+
             return new CopyCmdAnswer(e.toString());
         } finally {
             if (secondaryStoragePool != null) {
@@ -496,6 +522,13 @@ public class KVMStorageProcessor implements StorageProcessor {
 
     @Override
     public Answer createTemplateFromVolume(final CopyCommand cmd) {
+        Map<String, String> details = cmd.getOptions();
+
+        if (details != null && details.get(DiskTO.IQN) != null) {
+            // use the managed-storage approach
+            return createTemplateFromVolumeOrSnapshot(cmd);
+        }
+
         final DataTO srcData = cmd.getSrcTO();
         final DataTO destData = cmd.getDestTO();
         final int wait = cmd.getWaitInMillSeconds();
@@ -510,7 +543,8 @@ public class KVMStorageProcessor implements StorageProcessor {
         final NfsTO nfsImageStore = (NfsTO)imageStore;
 
         KVMStoragePool secondaryStorage = null;
-        KVMStoragePool primary = null;
+        KVMStoragePool primary;
+
         try {
             final String templateFolder = template.getPath();
 
@@ -614,8 +648,139 @@ public class KVMStorageProcessor implements StorageProcessor {
     }
 
     @Override
-    public Answer createTemplateFromSnapshot(final CopyCommand cmd) {
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    public Answer createTemplateFromSnapshot(CopyCommand cmd) {
+        Map<String, String> details = cmd.getOptions();
+
+        if (details != null && details.get(DiskTO.IQN) != null) {
+            // use the managed-storage approach
+            return createTemplateFromVolumeOrSnapshot(cmd);
+        }
+
+        return new CopyCmdAnswer("operation not supported");
+    }
+
+    private Answer createTemplateFromVolumeOrSnapshot(CopyCommand cmd) {
+        DataTO srcData = cmd.getSrcTO();
+
+        final boolean isVolume;
+
+        if (srcData instanceof VolumeObjectTO) {
+            isVolume = true;
+        }
+        else if (srcData instanceof SnapshotObjectTO) {
+            isVolume = false;
+        }
+        else {
+            return new CopyCmdAnswer("unsupported object type");
+        }
+
+        PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO)srcData.getDataStore();
+
+        DataTO destData = cmd.getDestTO();
+        TemplateObjectTO template = (TemplateObjectTO)destData;
+        DataStoreTO imageStore = template.getDataStore();
+
+        if (!(imageStore instanceof NfsTO)) {
+            return new CopyCmdAnswer("unsupported protocol");
+        }
+
+        NfsTO nfsImageStore = (NfsTO)imageStore;
+
+        KVMStoragePool secondaryStorage = null;
+
+        try {
+            Map<String, String> details = cmd.getOptions();
+
+            String path = details != null ? details.get(DiskTO.IQN) : null;
+
+            if (path == null) {
+                new CloudRuntimeException("The 'path' field must be specified.");
+            }
+
+            storagePoolMgr.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path, details);
+
+            KVMPhysicalDisk srcDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
+
+            secondaryStorage = storagePoolMgr.getStoragePoolByURI(nfsImageStore.getUrl());
+
+            String templateFolder = template.getPath();
+            String tmpltPath = secondaryStorage.getLocalPath() + File.separator + templateFolder;
+
+            storageLayer.mkdirs(tmpltPath);
+
+            String templateName = UUID.randomUUID().toString();
+
+            s_logger.debug("Converting " + srcDisk.getFormat().toString() + " disk " + srcDisk.getPath() + " into template " + templateName);
+
+            String destName = templateFolder + "/" + templateName + ".qcow2";
+
+            storagePoolMgr.copyPhysicalDisk(srcDisk, destName, secondaryStorage, cmd.getWaitInMillSeconds());
+
+            File templateProp = new File(tmpltPath + "/template.properties");
+
+            if (!templateProp.exists()) {
+                templateProp.createNewFile();
+            }
+
+            String templateContent = "filename=" + templateName + ".qcow2" + System.getProperty("line.separator");
+
+            DateFormat dateFormat = new SimpleDateFormat("MM_dd_yyyy");
+            Date date = new Date();
+
+            if (isVolume) {
+                templateContent += "volume.name=" + dateFormat.format(date) + System.getProperty("line.separator");
+            }
+            else {
+                templateContent += "snapshot.name=" + dateFormat.format(date) + System.getProperty("line.separator");
+            }
+
+            FileOutputStream templFo = new FileOutputStream(templateProp);
+
+            templFo.write(templateContent.getBytes());
+            templFo.flush();
+            templFo.close();
+
+            Map<String, Object> params = new HashMap<>();
+
+            params.put(StorageLayer.InstanceConfigKey, storageLayer);
+
+            Processor qcow2Processor = new QCOW2Processor();
+
+            qcow2Processor.configure("QCOW2 Processor", params);
+
+            FormatInfo info = qcow2Processor.process(tmpltPath, null, templateName);
+
+            TemplateLocation loc = new TemplateLocation(storageLayer, tmpltPath);
+
+            loc.create(1, true, templateName);
+            loc.addFormat(info);
+            loc.save();
+
+            storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
+
+            TemplateObjectTO newTemplate = new TemplateObjectTO();
+
+            newTemplate.setPath(templateFolder + File.separator + templateName + ".qcow2");
+            newTemplate.setSize(info.virtualSize);
+            newTemplate.setPhysicalSize(info.size);
+            newTemplate.setFormat(ImageFormat.QCOW2);
+            newTemplate.setName(templateName);
+
+            return new CopyCmdAnswer(newTemplate);
+        } catch (Exception ex) {
+            if (isVolume) {
+                s_logger.debug("Failed to create template from volume: ", ex);
+            }
+            else {
+                s_logger.debug("Failed to create template from snapshot: ", ex);
+            }
+
+            return new CopyCmdAnswer(ex.toString());
+        } finally {
+            if (secondaryStorage != null) {
+                secondaryStorage.delete();
+            }
+        }
     }
 
     protected String copyToS3(final File srcFile, final S3TO destStore, final String destPath) throws InterruptedException {
@@ -1327,7 +1492,17 @@ public class KVMStorageProcessor implements StorageProcessor {
             final String primaryUuid = pool.getUuid();
             final KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(pool.getPoolType(), primaryUuid);
             final String volUuid = UUID.randomUUID().toString();
-            final KVMPhysicalDisk disk = storagePoolMgr.copyPhysicalDisk(snapshotDisk, volUuid, primaryPool, cmd.getWaitInMillSeconds());
+
+            Map<String, String> details = cmd.getOptions2();
+
+            String path = details != null ? details.get(DiskTO.IQN) : null;
+
+            storagePoolMgr.connectPhysicalDisk(pool.getPoolType(), pool.getUuid(), path, details);
+
+            KVMPhysicalDisk disk = storagePoolMgr.copyPhysicalDisk(snapshotDisk, path != null ? path : volUuid, primaryPool, cmd.getWaitInMillSeconds());
+
+            storagePoolMgr.disconnectPhysicalDisk(pool.getPoolType(), pool.getUuid(), path);
+
             final VolumeObjectTO newVol = new VolumeObjectTO();
             newVol.setPath(disk.getName());
             newVol.setSize(disk.getVirtualSize());
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
index 6a0430f..792fc69 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
@@ -761,6 +761,12 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
+        // this is for managed storage that needs to cleanup disks after use
+        return false;
+    }
+
+    @Override
     public boolean disconnectPhysicalDiskByPath(String localPath) {
         // we've only ever cleaned up ISOs that are NFS mounted
         String poolUuid = null;
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java
index 72edb13..596582d 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java
@@ -261,6 +261,11 @@ public class ManagedNfsStorageAdaptor implements StorageAdaptor {
     }
 
     @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
+        return false;
+    }
+
+    @Override
     public boolean disconnectPhysicalDiskByPath(String localPath) {
         return false;
     }
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java
index 43f94fb..2c1ed23 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java
@@ -48,6 +48,8 @@ public interface StorageAdaptor {
     // given disk path (per database) and pool, clean up disk on host
     public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool);
 
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect);
+
     // given local path to file/device (per Libvirt XML), 1) check that device is
     // handled by your adaptor, return false if not. 2) clean up device, return true
     public boolean disconnectPhysicalDiskByPath(String localPath);
diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java
index 6248b7c..d2e8b91 100644
--- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java
+++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java
@@ -253,6 +253,7 @@ import com.cloud.hypervisor.vmware.mo.DatastoreMO;
 import com.cloud.hypervisor.vmware.mo.DiskControllerType;
 import com.cloud.hypervisor.vmware.mo.FeatureKeyConstants;
 import com.cloud.hypervisor.vmware.mo.HostMO;
+import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO;
 import com.cloud.hypervisor.vmware.mo.HostStorageSystemMO;
 import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper;
 import com.cloud.hypervisor.vmware.mo.NetworkDetails;
@@ -308,6 +309,8 @@ import com.cloud.agent.api.GetVmIpAddressCommand;
 public class VmwareResource implements StoragePoolResource, ServerResource, VmwareHostService, VirtualRouterDeployer {
     private static final Logger s_logger = Logger.getLogger(VmwareResource.class);
 
+    private static final Random RANDOM = new Random(System.nanoTime());
+
     protected String _name;
 
     protected final long _opsTimeout = 900000;   // 15 minutes time out to time
@@ -680,10 +683,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         boolean useWorkerVm = false;
 
         VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext());
-        String poolId = cmd.getPoolUuid();
         VirtualMachineMO vmMo = null;
-        DatastoreMO dsMo = null;
-        ManagedObjectReference morDS = null;
+
         String vmdkDataStorePath = null;
 
         try {
@@ -693,43 +694,80 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
             } else if (newSize == oldSize) {
                 return new ResizeVolumeAnswer(cmd, true, "success", newSize * ResourceType.bytesToKiB);
             }
+
             if (vmName.equalsIgnoreCase("none")) {
-                // we need to spawn a worker VM to attach the volume to and
-                // resize the volume.
+                // we need to spawn a worker VM to attach the volume to and resize the volume.
                 useWorkerVm = true;
                 vmName = getWorkerName(getServiceContext(), cmd, 0);
 
-                morDS = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, poolId);
-                dsMo = new DatastoreMO(hyperHost.getContext(), morDS);
+                String poolId = cmd.getPoolUuid();
+
+                ManagedObjectReference morDS = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, poolId);
+                DatastoreMO dsMo = new DatastoreMO(hyperHost.getContext(), morDS);
+
                 s_logger.info("Create worker VM " + vmName);
+
                 vmMo = HypervisorHostHelper.createWorkerVM(hyperHost, dsMo, vmName);
+
                 if (vmMo == null) {
                     throw new Exception("Unable to create a worker VM for volume resize");
                 }
 
                 synchronized (this) {
                     vmdkDataStorePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, path + ".vmdk");
-                    vmMo.attachDisk(new String[] {vmdkDataStorePath}, morDS);
+
+                    vmMo.attachDisk(new String[] { vmdkDataStorePath }, morDS);
                 }
             }
+
             // find VM through datacenter (VM is not at the target host yet)
             vmMo = hyperHost.findVmOnPeerHyperHost(vmName);
+
             if (vmMo == null) {
                 String msg = "VM " + vmName + " does not exist in VMware datacenter";
+
                 s_logger.error(msg);
+
                 throw new Exception(msg);
             }
 
             Pair<VirtualDisk, String> vdisk = vmMo.getDiskDevice(path);
+
             if (vdisk == null) {
-                if (s_logger.isTraceEnabled())
+                if (s_logger.isTraceEnabled()) {
                     s_logger.trace("resize volume done (failed)");
+                }
+
                 throw new Exception("No such disk device: " + path);
             }
+
             // IDE virtual disk cannot be re-sized if VM is running
             if (vdisk.second() != null && vdisk.second().contains("ide")) {
-                throw new Exception("Re-sizing a virtual disk over IDE controller is not supported in VMware hypervisor. "
-                        + "Please re-try when virtual disk is attached to a VM using SCSI controller.");
+                throw new Exception("Re-sizing a virtual disk over an IDE controller is not supported in the VMware hypervisor. " +
+                            "Please re-try when virtual disk is attached to a VM using a SCSI controller.");
+            }
+
+            if (cmd.isManaged()) {
+                VmwareContext context = getServiceContext();
+
+                ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
+                ClusterMO clusterMO = new ClusterMO(context, morCluster);
+
+                List<Pair<ManagedObjectReference, String>> lstHosts = clusterMO.getClusterHosts();
+
+                Collections.shuffle(lstHosts, RANDOM);
+
+                Pair<ManagedObjectReference, String> host = lstHosts.get(0);
+
+                HostMO hostMO = new HostMO(context, host.first());
+                HostDatastoreSystemMO hostDatastoreSystem = hostMO.getHostDatastoreSystemMO();
+
+                String iScsiName = cmd.get_iScsiName();
+
+                ManagedObjectReference morDS = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, VmwareResource.getDatastoreName(iScsiName));
+                DatastoreMO dsMo = new DatastoreMO(hyperHost.getContext(), morDS);
+
+                _storageProcessor.expandDatastore(hostDatastoreSystem, dsMo);
             }
 
             if (vdisk.second() != null && !vdisk.second().toLowerCase().startsWith("scsi"))
@@ -744,17 +782,22 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
                 throw new Exception("Resize is not supported because Disk device has Parent "+ ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent().getUuid());
             }
             String vmdkAbsFile = getAbsoluteVmdkFile(disk);
+
             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
                 vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
             }
 
             disk.setCapacityInKB(newSize);
 
-            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
             VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
+
             deviceConfigSpec.setDevice(disk);
             deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
+
+            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
+
             vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
+
             if (!vmMo.configureVm(vmConfigSpec)) {
                 throw new Exception("Failed to configure VM to resize disk. vmName: " + vmName);
             }
@@ -762,12 +805,15 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
             return new ResizeVolumeAnswer(cmd, true, "success", newSize * 1024);
         } catch (Exception e) {
             s_logger.error("Unable to resize volume", e);
+
             String error = "Failed to resize volume: " + e.getMessage();
+
             return new ResizeVolumeAnswer(cmd, false, error);
         } finally {
             try {
-                if (useWorkerVm == true) {
+                if (useWorkerVm) {
                     s_logger.info("Destroy worker VM after volume resize");
+
                     vmMo.detachDisk(vmdkDataStorePath, false);
                     vmMo.destroy();
                 }
@@ -2190,9 +2236,9 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
             vmMo.setCustomFieldValue(CustomFieldConstants.CLOUD_NIC_MASK, String.valueOf(nicMask));
             postNvpConfigBeforeStart(vmMo, vmSpec);
 
-            Map<String, String> iqnToPath = new HashMap<String, String>();
+            Map<String, Map<String, String>> iqnToData = new HashMap<>();
 
-            postDiskConfigBeforeStart(vmMo, vmSpec, sortedDisks, ideControllerKey, scsiControllerKey, iqnToPath, hyperHost, context);
+            postDiskConfigBeforeStart(vmMo, vmSpec, sortedDisks, ideControllerKey, scsiControllerKey, iqnToData, hyperHost, context);
 
             //
             // Power-on VM
@@ -2203,7 +2249,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
 
             StartAnswer startAnswer = new StartAnswer(cmd);
 
-            startAnswer.setIqnToPath(iqnToPath);
+            startAnswer.setIqnToData(iqnToData);
 
             // Since VM was successfully powered-on, if there was an existing VM in a different cluster that was unregistered, delete all the files associated with it.
             if (existingVmName != null && existingVmFileLayout != null) {
@@ -2460,11 +2506,21 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         final String datastoreDiskPath;
 
         if (isManaged) {
+            String vmdkPath = new DatastoreFile(volumeTO.getPath()).getFileBaseName();
+
             if (volumeTO.getVolumeType() == Volume.Type.ROOT) {
-                datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, volumeTO.getName(), VmwareManager.s_vmwareSearchExcludeFolder.value());
+                if (vmdkPath == null) {
+                    vmdkPath = volumeTO.getName();
+                }
+
+                datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, vmdkPath);
             }
             else {
-                datastoreDiskPath = dsMo.getDatastorePath(dsMo.getName() + ".vmdk");
+                if (vmdkPath == null) {
+                    vmdkPath = dsMo.getName();
+                }
+
+                datastoreDiskPath = dsMo.getDatastorePath(vmdkPath + ".vmdk");
             }
         } else {
             datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, volumeTO.getPath(), VmwareManager.s_vmwareSearchExcludeFolder.value());
@@ -2822,8 +2878,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         }
     }
 
-    private void postDiskConfigBeforeStart(VirtualMachineMO vmMo, VirtualMachineTO vmSpec, DiskTO[] sortedDisks, int ideControllerKey, int scsiControllerKey,
-            Map<String, String> iqnToPath, VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception {
+    private void postDiskConfigBeforeStart(VirtualMachineMO vmMo, VirtualMachineTO vmSpec, DiskTO[] sortedDisks, int ideControllerKey,
+            int scsiControllerKey, Map<String, Map<String, String>> iqnToData, VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception {
         VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder();
 
         for (DiskTO vol : sortedDisks) {
@@ -2862,10 +2918,18 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
             }
 
             VolumeObjectTO volInSpec = getVolumeInSpec(vmSpec, volumeTO);
+
             if (volInSpec != null) {
                 if (managed) {
+                    Map<String, String> data = new HashMap<>();
+
                     String datastoreVolumePath = diskChain[0];
-                    iqnToPath.put(details.get(DiskTO.IQN), datastoreVolumePath);
+
+                    data.put(StartAnswer.PATH, datastoreVolumePath);
+                    data.put(StartAnswer.IMAGE_FORMAT, Storage.ImageFormat.OVA.toString());
+
+                    iqnToData.put(details.get(DiskTO.IQN), data);
+
                     vol.setPath(datastoreVolumePath);
                     volumeTO.setPath(datastoreVolumePath);
                     volInSpec.setPath(datastoreVolumePath);
@@ -2972,9 +3036,39 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         return listForSort.toArray(new DiskTO[0]);
     }
 
-    private HashMap<String, Pair<ManagedObjectReference, DatastoreMO>> inferDatastoreDetailsFromDiskInfo(VmwareHypervisorHost hyperHost, VmwareContext context, DiskTO[] disks,
-            Command cmd) throws Exception {
-        HashMap<String, Pair<ManagedObjectReference, DatastoreMO>> mapIdToMors = new HashMap<String, Pair<ManagedObjectReference, DatastoreMO>>();
+    /**
+     * Only call this for managed storage.
+     * Ex. "[-iqn.2010-01.com.solidfire:4nhe.vol-1.27-0] i-2-18-VM/ROOT-18.vmdk" should return "i-2-18-VM/ROOT-18"
+     */
+    public String getVmdkPath(String path) {
+        if (!com.cloud.utils.StringUtils.isNotBlank(path)) {
+            return null;
+        }
+
+        final String search = "]";
+
+        int startIndex = path.indexOf(search);
+
+        if (startIndex == -1) {
+            return null;
+        }
+
+        path = path.substring(startIndex + search.length());
+
+        final String search2 = ".vmdk";
+
+        int endIndex = path.indexOf(search2);
+
+        if (endIndex == -1) {
+            return null;
+        }
+
+        return path.substring(0, endIndex).trim();
+    }
+
+    private HashMap<String, Pair<ManagedObjectReference, DatastoreMO>> inferDatastoreDetailsFromDiskInfo(VmwareHypervisorHost hyperHost, VmwareContext context,
+            DiskTO[] disks, Command cmd) throws Exception {
+        HashMap<String, Pair<ManagedObjectReference, DatastoreMO>> mapIdToMors = new HashMap<>();
 
         assert (hyperHost != null) && (context != null);
 
@@ -3000,20 +3094,33 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
                         // if the datastore is not present, we need to discover the iSCSI device that will support it,
                         // create the datastore, and create a VMDK file in the datastore
                         if (morDatastore == null) {
-                            morDatastore = _storageProcessor.prepareManagedStorage(context, hyperHost, null, iScsiName, details.get(DiskTO.STORAGE_HOST),
-                                    Integer.parseInt(details.get(DiskTO.STORAGE_PORT)), volumeTO.getVolumeType() == Volume.Type.ROOT ? volumeTO.getName() : null,
-                                    details.get(DiskTO.CHAP_INITIATOR_USERNAME), details.get(DiskTO.CHAP_INITIATOR_SECRET), details.get(DiskTO.CHAP_TARGET_USERNAME),
-                                    details.get(DiskTO.CHAP_TARGET_SECRET), Long.parseLong(details.get(DiskTO.VOLUME_SIZE)), cmd);
+                            final String vmdkPath = getVmdkPath(volumeTO.getPath());
+
+                            morDatastore = _storageProcessor.prepareManagedStorage(context, hyperHost, null, iScsiName,
+                                    details.get(DiskTO.STORAGE_HOST), Integer.parseInt(details.get(DiskTO.STORAGE_PORT)),
+                                    vmdkPath,
+                                    details.get(DiskTO.CHAP_INITIATOR_USERNAME), details.get(DiskTO.CHAP_INITIATOR_SECRET),
+                                    details.get(DiskTO.CHAP_TARGET_USERNAME), details.get(DiskTO.CHAP_TARGET_SECRET),
+                                    Long.parseLong(details.get(DiskTO.VOLUME_SIZE)), cmd);
 
                             DatastoreMO dsMo = new DatastoreMO(getServiceContext(), morDatastore);
-                            String datastoreVolumePath = dsMo.getDatastorePath((volumeTO.getVolumeType() == Volume.Type.ROOT ? volumeTO.getName() : dsMo.getName()) + ".vmdk");
+
+                            final String datastoreVolumePath;
+
+                            if (vmdkPath != null) {
+                                datastoreVolumePath = dsMo.getDatastorePath(vmdkPath + ".vmdk");
+                            }
+                            else {
+                                datastoreVolumePath = dsMo.getDatastorePath(dsMo.getName() + ".vmdk");
+                            }
 
                             volumeTO.setPath(datastoreVolumePath);
                             vol.setPath(datastoreVolumePath);
                         }
 
-                        mapIdToMors.put(datastoreName, new Pair<ManagedObjectReference, DatastoreMO>(morDatastore, new DatastoreMO(context, morDatastore)));
-                    } else {
+                        mapIdToMors.put(datastoreName, new Pair<>(morDatastore, new DatastoreMO(context, morDatastore)));
+                    }
+                    else {
                         ManagedObjectReference morDatastore = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, poolUuid);
 
                         if (morDatastore == null) {
@@ -3024,7 +3131,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
                             throw new Exception(msg);
                         }
 
-                        mapIdToMors.put(poolUuid, new Pair<ManagedObjectReference, DatastoreMO>(morDatastore, new DatastoreMO(context, morDatastore)));
+                        mapIdToMors.put(poolUuid, new Pair<>(morDatastore, new DatastoreMO(context, morDatastore)));
                     }
                 }
             }
@@ -4100,9 +4207,35 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
     }
 
     protected Answer execute(ModifyTargetsCommand cmd) {
-        VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext());
+        VmwareContext context = getServiceContext(cmd);
+        VmwareHypervisorHost hyperHost = getHyperHost(context);
+
+        List<HostMO> hostMOs = new ArrayList<>();
+
+        if (cmd.getApplyToAllHostsInCluster()) {
+            try {
+                ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
+                ClusterMO clusterMO = new ClusterMO(context, morCluster);
+
+                List<Pair<ManagedObjectReference, String>> hosts = clusterMO.getClusterHosts();
+
+                for (Pair<ManagedObjectReference, String> host : hosts) {
+                    HostMO hostMO = new HostMO(context, host.first());
 
-        handleTargets(cmd.getAdd(), cmd.getTargets(), (HostMO)hyperHost);
+                    hostMOs.add(hostMO);
+                }
+            }
+            catch (Exception ex) {
+                s_logger.error(ex.getMessage(), ex);
+
+                throw new CloudRuntimeException(ex.getMessage(), ex);
+            }
+        }
+        else {
+            hostMOs.add((HostMO)hyperHost);
+        }
+
+        handleTargets(cmd.getAdd(), cmd.getTargetTypeToRemove(), cmd.isRemoveAsync(), cmd.getTargets(), hostMOs);
 
         return new ModifyTargetsAnswer();
     }
@@ -4133,7 +4266,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
             long capacity = summary.getCapacity();
             long available = summary.getFreeSpace();
 
-            Map<String, TemplateProp> tInfo = new HashMap<String, TemplateProp>();
+            Map<String, TemplateProp> tInfo = new HashMap<>();
             ModifyStoragePoolAnswer answer = new ModifyStoragePoolAnswer(cmd, capacity, available, tInfo);
 
             if (cmd.getAdd() && pool.getType() == StoragePoolType.VMFS) {
@@ -4156,11 +4289,13 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         }
     }
 
-    private void handleTargets(boolean add, List<Map<String, String>> targets, HostMO host) {
+    private void handleTargets(boolean add, ModifyTargetsCommand.TargetTypeToRemove targetTypeToRemove, boolean isRemoveAsync,
+                               List<Map<String, String>> targets, List<HostMO> hosts) {
         if (targets != null && targets.size() > 0) {
             try {
-                _storageProcessor.handleTargetsForHost(add, targets, host);
-            } catch (Exception ex) {
+                _storageProcessor.handleTargets(add, targetTypeToRemove, isRemoveAsync, targets, hosts);
+            }
+            catch (Exception ex) {
                 s_logger.warn(ex.getMessage());
             }
         }
@@ -4173,8 +4308,9 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
 
         try {
             if (cmd.getRemoveDatastore()) {
-                _storageProcessor.handleDatastoreAndVmdkDetach(cmd.getDetails().get(DeleteStoragePoolCommand.DATASTORE_NAME), cmd.getDetails().get(DeleteStoragePoolCommand.IQN),
-                        cmd.getDetails().get(DeleteStoragePoolCommand.STORAGE_HOST), Integer.parseInt(cmd.getDetails().get(DeleteStoragePoolCommand.STORAGE_PORT)));
+                _storageProcessor.handleDatastoreAndVmdkDetach(cmd, cmd.getDetails().get(DeleteStoragePoolCommand.DATASTORE_NAME),
+                        cmd.getDetails().get(DeleteStoragePoolCommand.IQN), cmd.getDetails().get(DeleteStoragePoolCommand.STORAGE_HOST),
+                        Integer.parseInt(cmd.getDetails().get(DeleteStoragePoolCommand.STORAGE_PORT)));
 
                 return new Answer(cmd, true, "success");
             } else {
@@ -4204,6 +4340,10 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         return str.replace('/', '-');
     }
 
+    public static String createDatastoreNameFromIqn(String iqn) {
+        return "-" + iqn + "-0";
+    }
+
     protected AttachIsoAnswer execute(AttachIsoCommand cmd) {
         if (s_logger.isInfoEnabled()) {
             s_logger.info("Executing resource AttachIsoCommand: " + _gson.toJson(cmd));
@@ -5119,7 +5259,11 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
 
                 for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) {
                     if (hba instanceof HostInternetScsiHba) {
-                        return ((HostInternetScsiHba)hba).getIScsiName();
+                        HostInternetScsiHba hostInternetScsiHba = (HostInternetScsiHba)hba;
+
+                        if (hostInternetScsiHba.isIsSoftwareBased()) {
+                            return ((HostInternetScsiHba)hba).getIScsiName();
+                        }
                     }
                 }
             }
@@ -5991,7 +6135,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
         return dcMo.findVm(vol.getPath());
     }
 
-    private String getAbsoluteVmdkFile(VirtualDisk disk) {
+    public String getAbsoluteVmdkFile(VirtualDisk disk) {
         String vmdkAbsFile = null;
         VirtualDeviceBackingInfo backingInfo = disk.getBacking();
         if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
diff --git a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java
index 9cbc7a7..0cea62f 100644
--- a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java
+++ b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -39,19 +40,32 @@ import org.apache.log4j.Logger;
 
 import com.google.common.base.Strings;
 import com.google.gson.Gson;
+import com.vmware.vim25.DatastoreHostMount;
 import com.vmware.vim25.HostHostBusAdapter;
 import com.vmware.vim25.HostInternetScsiHba;
 import com.vmware.vim25.HostInternetScsiHbaAuthenticationProperties;
+import com.vmware.vim25.HostInternetScsiHbaSendTarget;
 import com.vmware.vim25.HostInternetScsiHbaStaticTarget;
 import com.vmware.vim25.HostInternetScsiTargetTransport;
+import com.vmware.vim25.HostResignatureRescanResult;
+import com.vmware.vim25.HostUnresolvedVmfsResignatureSpec;
 import com.vmware.vim25.HostScsiDisk;
 import com.vmware.vim25.HostScsiTopology;
 import com.vmware.vim25.HostScsiTopologyInterface;
 import com.vmware.vim25.HostScsiTopologyLun;
 import com.vmware.vim25.HostScsiTopologyTarget;
+import com.vmware.vim25.HostUnresolvedVmfsExtent;
+import com.vmware.vim25.HostUnresolvedVmfsVolume;
+import com.vmware.vim25.InvalidStateFaultMsg;
 import com.vmware.vim25.ManagedObjectReference;
+import com.vmware.vim25.VirtualDeviceBackingInfo;
+import com.vmware.vim25.VirtualDeviceConfigSpec;
+import com.vmware.vim25.VirtualDeviceConfigSpecOperation;
+import com.vmware.vim25.VirtualMachineConfigSpec;
 import com.vmware.vim25.VirtualDisk;
 import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
+import com.vmware.vim25.VmfsDatastoreExpandSpec;
+import com.vmware.vim25.VmfsDatastoreOption;
 
 import org.apache.cloudstack.storage.command.AttachAnswer;
 import org.apache.cloudstack.storage.command.AttachCommand;
@@ -141,9 +155,10 @@ public class VmwareStorageProcessor implements StorageProcessor {
     private final Gson _gson;
     private final StorageLayer _storage = new JavaStorageLayer();
     private Integer _nfsVersion;
+    private static final Random RANDOM = new Random(System.nanoTime());
 
     public VmwareStorageProcessor(VmwareHostService hostService, boolean fullCloneFlag, VmwareStorageMount mountService, Integer timeout, VmwareResource resource,
-            Integer shutdownWaitMs, PremiumSecondaryStorageResource storageResource, Integer nfsVersion) {
+                                  Integer shutdownWaitMs, PremiumSecondaryStorageResource storageResource, Integer nfsVersion) {
         this.hostService = hostService;
         _fullCloneFlag = fullCloneFlag;
         this.mountService = mountService;
@@ -163,9 +178,288 @@ public class VmwareStorageProcessor implements StorageProcessor {
 
     @Override
     public ResignatureAnswer resignature(ResignatureCommand cmd) {
-        s_logger.info("'ResignatureAnswer resignature(ResignatureCommand)' not currently used for VmwareStorageProcessor");
+        final Map<String, String> details = cmd.getDetails();
 
-        return new ResignatureAnswer();
+        String scsiNaaDeviceId = details.get(DiskTO.SCSI_NAA_DEVICE_ID);
+
+        if (scsiNaaDeviceId == null || scsiNaaDeviceId.trim().length() == 0) {
+            throw new CloudRuntimeException("The 'scsiNaaDeviceId' needs to be specified when resignaturing a VMware datastore.");
+        }
+
+        final String iScsiName = details.get(DiskTO.IQN);
+        final String datastoreName = getMaximumDatastoreName(VmwareResource.getDatastoreName(iScsiName));
+
+        String vmdk = null;
+
+        try {
+            VmwareContext context = hostService.getServiceContext(null);
+            VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
+
+            ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
+            ClusterMO clusterMO = new ClusterMO(context, morCluster);
+
+            List<Pair<ManagedObjectReference, String>> lstHosts = clusterMO.getClusterHosts();
+
+            // add iSCSI connection to host
+
+            final String storageHost = details.get(DiskTO.STORAGE_HOST);
+            final int storagePortNumber = Integer.parseInt(details.get(DiskTO.STORAGE_PORT));
+            final String chapInitiatorUsername = details.get(DiskTO.CHAP_INITIATOR_USERNAME);
+            final String chapInitiatorSecret = details.get(DiskTO.CHAP_INITIATOR_SECRET);
+            final String chapTargetUsername = details.get(DiskTO.CHAP_TARGET_USERNAME);
+            final String chapTargetSecret = details.get(DiskTO.CHAP_TARGET_SECRET);
+
+            HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(context, storageHost, lstHosts);
+            List<HostMO> hostsUsingStaticDiscovery = hostDiscoveryMethod.getHostsUsingStaticDiscovery();
+
+            if (hostsUsingStaticDiscovery != null && hostsUsingStaticDiscovery.size() > 0) {
+                List<HostInternetScsiHbaStaticTarget> lstTargets = getTargets(storageHost, storagePortNumber, trimIqn(iScsiName),
+                        chapInitiatorUsername, chapInitiatorSecret, chapTargetUsername, chapTargetSecret);
+
+                addRemoveInternetScsiTargetsToAllHosts(true, lstTargets, hostsUsingStaticDiscovery);
+            }
+
+            rescanAllHosts(context, lstHosts, true, true);
+
+            // perform resignature operation
+
+            HostMO hostMO = new HostMO(context, lstHosts.get(0).first());
+
+            HostDatastoreSystemMO hostDatastoreSystem = hostMO.getHostDatastoreSystemMO();
+
+            List<HostUnresolvedVmfsVolume> hostUnresolvedVmfsVolumes = hostDatastoreSystem.queryUnresolvedVmfsVolumes();
+
+            if (hostUnresolvedVmfsVolumes == null || hostUnresolvedVmfsVolumes.size() == 0) {
+                throw new CloudRuntimeException("Unable to locate any snapshot datastores");
+            }
+
+            boolean foundExtent = false;
+
+            for (HostUnresolvedVmfsVolume hostUnresolvedVmfsVolume : hostUnresolvedVmfsVolumes) {
+                List<HostUnresolvedVmfsExtent> extents = hostUnresolvedVmfsVolume.getExtent();
+                List<HostUnresolvedVmfsExtent> matchingExtents = getExtentsMatching(extents, scsiNaaDeviceId);
+
+                if (matchingExtents.size() >= 1) {
+                    String extentDevicePath = matchingExtents.get(0).getDevicePath();
+                    HostResignatureRescanResult hostResignatureRescanResult = resignatureDatastore(hostDatastoreSystem, extentDevicePath);
+
+                    if (hostResignatureRescanResult == null) {
+                        throw new CloudRuntimeException("'hostResignatureRescanResult' should not be 'null'.");
+                    }
+
+                    ManagedObjectReference morDs = hostResignatureRescanResult.getResult();
+
+                    if (morDs == null) {
+                        throw new CloudRuntimeException("'morDs' should not be 'null'.");
+                    }
+
+                    DatastoreMO datastoreMO = new DatastoreMO(context, morDs);
+
+                    boolean isOnlyForTemplate = Boolean.parseBoolean(details.get(DiskTO.TEMPLATE_RESIGN));
+
+                    // If this is only for a template, all we really want to do is resignature the datastore (done at this point),
+                    // then rename the datastore.
+                    if (isOnlyForTemplate) {
+                        vmdk = details.get(DiskTO.VMDK);
+                    }
+                    else {
+                        vmdk = cleanUpDatastore(cmd, hostDatastoreSystem, datastoreMO, details);
+                    }
+
+                    if (renameDatastore(context, morDs, datastoreName, lstHosts)) {
+                        foundExtent = true;
+
+                        break;
+                    }
+                }
+            }
+
+            removeVmfsDatastore(cmd, hyperHost, datastoreName, storageHost, storagePortNumber, trimIqn(iScsiName), lstHosts);
+
+            if (!foundExtent) {
+                throw new CloudRuntimeException("Unable to locate the applicable extent");
+            }
+
+            final ResignatureAnswer answer = new ResignatureAnswer();
+
+            final long volumeSize = Long.parseLong(details.get(DiskTO.VOLUME_SIZE));
+
+            answer.setSize(volumeSize);
+
+            answer.setPath("[" + datastoreName + "] " + vmdk);
+
+            answer.setFormat(ImageFormat.OVA);
+
+            return answer;
+        }
+        catch (Exception ex) {
+            s_logger.debug(ex.getMessage());
+
+            throw new CloudRuntimeException(ex.getMessage());
+        }
+    }
+
+    private List<HostUnresolvedVmfsExtent> getExtentsMatching(List<HostUnresolvedVmfsExtent> extents, String naa) {
+        List<HostUnresolvedVmfsExtent> matchingExtents = new ArrayList<>();
+
+        if (extents != null) {
+            for (HostUnresolvedVmfsExtent extent : extents) {
+                s_logger.debug("extent devicePath=" + extent.getDevicePath() + ", ordinal=" + extent.getOrdinal()
+                        + ", reason=" + extent.getReason() + ", isHeadExtent=" + extent.isIsHeadExtent());
+
+                String extentDevicePath = extent.getDevicePath();
+
+                if (extentDevicePath.contains(naa)) {
+                    matchingExtents.add(extent);
+                }
+            }
+        }
+
+        return matchingExtents;
+    }
+
+    private class HostUnresolvedVmfsResignatureSpecCustom extends HostUnresolvedVmfsResignatureSpec {
+        private HostUnresolvedVmfsResignatureSpecCustom(String extentDevicePath) {
+            this.extentDevicePath = new ArrayList<>(1);
+
+            this.extentDevicePath.add(extentDevicePath);
+        }
+    }
+
+    private HostResignatureRescanResult resignatureDatastore(HostDatastoreSystemMO hostDatastoreSystemMO, String extentDevicePath) throws Exception {
+        HostUnresolvedVmfsResignatureSpecCustom resignatureSpec = new HostUnresolvedVmfsResignatureSpecCustom(extentDevicePath);
+
+        return hostDatastoreSystemMO.resignatureUnresolvedVmfsVolume(resignatureSpec);
+    }
+
+    private boolean renameDatastore(VmwareContext context, ManagedObjectReference morDs, String newName, List<Pair<ManagedObjectReference, String>> lstHosts) throws Exception {
+        if (morDs != null) {
+            DatastoreMO datastoreMO = new DatastoreMO(context, morDs);
+
+            datastoreMO.renameDatastore(newName);
+
+            waitForAllHostsToMountDatastore(lstHosts, datastoreMO);
+
+            return true;
+        }
+
+        s_logger.debug("Unable to locate datastore to rename");
+
+        return false;
+    }
+
+    private String getMaximumDatastoreName(String datastoreName) {
+        final int maxDatastoreNameLength = 80;
+
+        return datastoreName.length() > maxDatastoreNameLength ? datastoreName.substring(0, maxDatastoreNameLength) : datastoreName;
+    }
+
+    /**
+     * 1) Possibly expand the datastore.
+     * 2) Possibly consolidate all relevant VMDK files into one VMDK file.
+     * 3) Possibly move the VMDK file to the root folder (may already be there).
+     * 4) If the VMDK file wasn't already in the root folder, then delete the folder the VMDK file was in.
+     * 5) Possibly rename the VMDK file (this will lead to there being a delta file with the new name and the
+     *    original file with the original name).
+     *
+     * Note: If the underlying VMDK file was for a root disk, the 'vmdk' parameter's value might look, for example,
+     *  like "i-2-32-VM/ROOT-32.vmdk".
+     *
+     * Note: If the underlying VMDK file was for a data disk, the 'vmdk' parameter's value might look, for example,
+     *  like "-iqn.2010-01.com.solidfire:4nhe.data-32.79-0.vmdk".
+     *
+     * Returns the (potentially new) name of the VMDK file.
+     */
+    private String cleanUpDatastore(Command cmd, HostDatastoreSystemMO hostDatastoreSystem, DatastoreMO dsMo, Map<String, String> details) throws Exception {
+        boolean expandDatastore = Boolean.parseBoolean(details.get(DiskTO.EXPAND_DATASTORE));
+
+        // A volume on the storage system holding a template uses a minimum hypervisor snapshot reserve value.
+        // When this volume is cloned to a new volume, the new volume can be expanded (to take a new hypervisor snapshot reserve value
+        // into consideration). If expandDatastore is true, we want to expand the datastore in the new volume to the size of the cloned volume.
+        // It's possible that expandDatastore might be true and there isn't any extra space in the cloned volume (if the hypervisor snapshot
+        // reserve value in use is set to the minimum for the cloned volume), but that's fine.
+        if (expandDatastore) {
+            expandDatastore(hostDatastoreSystem, dsMo);
+        }
+
+        String vmdk = details.get(DiskTO.VMDK);
+        String fullVmdkPath = new DatastoreFile(dsMo.getName(), vmdk).getPath();
+
+        VmwareContext context = hostService.getServiceContext(null);
+        VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
+
+        DatacenterMO dcMo = new DatacenterMO(context, hyperHost.getHyperHostDatacenter());
+
+        String vmName = getVmName(vmdk);
+
+        // If vmName is not null, then move all VMDK files out of this folder to the root folder and then delete the folder named vmName.
+        if (vmName != null) {
+            String workerVmName = hostService.getWorkerName(context, cmd, 0);
+
+            VirtualMachineMO vmMo = HypervisorHostHelper.createWorkerVM(hyperHost, dsMo, workerVmName);
+
+            if (vmMo == null) {
+                throw new Exception("Unable to create a worker VM for volume creation");
+            }
+
+            vmMo.attachDisk(new String[] { fullVmdkPath }, dsMo.getMor());
+
+            List<String> backingFiles = new ArrayList<>(1);
+
+            List<VirtualDisk> virtualDisks = vmMo.getVirtualDisks();
+
+            VirtualDisk virtualDisk = virtualDisks.get(0);
+
+            VirtualDeviceBackingInfo virtualDeviceBackingInfo = virtualDisk.getBacking();
+
+            while (virtualDeviceBackingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
+                VirtualDiskFlatVer2BackingInfo backingInfo = (VirtualDiskFlatVer2BackingInfo)virtualDeviceBackingInfo;
+
+                backingFiles.add(backingInfo.getFileName());
+
+                virtualDeviceBackingInfo = backingInfo.getParent();
+            }
+
+            vmMo.detachAllDisks();
+            vmMo.destroy();
+
+            VmwareStorageLayoutHelper.moveVolumeToRootFolder(dcMo, backingFiles);
+
+            vmdk = new DatastoreFile(vmdk).getFileName();
+
+            // Delete the folder the VMDK file was in.
+
+            DatastoreFile folderToDelete = new DatastoreFile(dsMo.getName(), vmName);
+
+            dsMo.deleteFolder(folderToDelete.getPath(), dcMo.getMor());
+        }
+
+        return vmdk;
+    }
+
+    /**
+     * Example input for the 'vmdk' parameter:
+     *  i-2-32-VM/ROOT-32.vmdk
+     *  -iqn.2010-01.com.solidfire:4nhe.data-32.79-0.vmdk
+     */
+    private String getVmName(String vmdk) {
+        int indexOf = vmdk.indexOf("/");
+
+        if (indexOf == -1) {
+            return null;
+        }
+
+        return vmdk.substring(0, indexOf).trim();
+    }
+
+    public void expandDatastore(HostDatastoreSystemMO hostDatastoreSystem, DatastoreMO datastoreMO) throws Exception {
+        List<VmfsDatastoreOption> vmfsDatastoreOptions = hostDatastoreSystem.queryVmfsDatastoreExpandOptions(datastoreMO);
+
+        if (vmfsDatastoreOptions != null && vmfsDatastoreOptions.size() > 0) {
+            VmfsDatastoreExpandSpec vmfsDatastoreExpandSpec = (VmfsDatastoreExpandSpec)vmfsDatastoreOptions.get(0).getSpec();
+
+            hostDatastoreSystem.expandVmfsDatastore(datastoreMO, vmfsDatastoreExpandSpec);
+        }
     }
 
     private String getOVFFilePath(String srcOVAFileName) {
@@ -184,8 +478,8 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     private Pair<VirtualMachineMO, Long> copyTemplateFromSecondaryToPrimary(VmwareHypervisorHost hyperHost, DatastoreMO datastoreMo, String secondaryStorageUrl,
-            String templatePathAtSecondaryStorage, String templateName, String templateUuid, boolean createSnapshot, Integer nfsVersion) throws Exception {
-
+                                                                            String templatePathAtSecondaryStorage, String templateName, String templateUuid,
+                                                                            boolean createSnapshot, Integer nfsVersion) throws Exception {
         s_logger.info("Executing copyTemplateFromSecondaryToPrimary. secondaryStorage: " + secondaryStorageUrl + ", templatePathAtSecondaryStorage: " +
                 templatePathAtSecondaryStorage + ", templateName: " + templateName);
 
@@ -225,13 +519,13 @@ public class VmwareStorageProcessor implements StorageProcessor {
         if (vmMo == null) {
             String msg =
                     "Failed to import OVA template. secondaryStorage: " + secondaryStorageUrl + ", templatePathAtSecondaryStorage: " + templatePathAtSecondaryStorage +
-                    ", templateName: " + templateName + ", templateUuid: " + templateUuid;
+                            ", templateName: " + templateName + ", templateUuid: " + templateUuid;
             s_logger.error(msg);
             throw new Exception(msg);
         }
 
         OVAProcessor processor = new OVAProcessor();
-        Map<String, Object> params = new HashMap<String, Object>();
+        Map<String, Object> params = new HashMap<>();
         params.put(StorageLayer.InstanceConfigKey, _storage);
         processor.configure("OVA Processor", params);
         long virtualSize = processor.getTemplateVirtualSize(secondaryMountPoint + "/" + templatePathAtSecondaryStorage, templateName);
@@ -253,7 +547,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
             }
         }
 
-        return new Pair<VirtualMachineMO, Long>(vmMo, new Long(virtualSize));
+        return new Pair<>(vmMo, virtualSize);
     }
 
     @Override
@@ -273,7 +567,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
 
         String secondaryStorageUrl = nfsImageStore.getUrl();
 
-        assert (secondaryStorageUrl != null);
+        assert secondaryStorageUrl != null;
 
         boolean managed = false;
         String storageHost = null;
@@ -317,17 +611,19 @@ public class VmwareStorageProcessor implements StorageProcessor {
         Pair<String, String> templateInfo = VmwareStorageLayoutHelper.decodeTemplateRelativePathAndNameFromUrl(secondaryStorageUrl, templateUrl, template.getName());
 
         VmwareContext context = hostService.getServiceContext(cmd);
+
         if (context == null) {
-            return new CopyCmdAnswer("Failed to create a Vmware context, check the management server logs or the ssvm log for details");
+            return new CopyCmdAnswer("Failed to create a VMware context, check the management server logs or the SSVM log for details");
         }
 
+        VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd);
+        DatastoreMO dsMo = null;
+
         try {
-            VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd);
             String storageUuid = managed ? managedStoragePoolName : primaryStore.getUuid();
             String templateUuidName = deriveTemplateUuidOnHost(hyperHost, storageUuid, templateInfo.second());
             DatacenterMO dcMo = new DatacenterMO(context, hyperHost.getHyperHostDatacenter());
             VirtualMachineMO templateMo = VmwareHelper.pickOneVmOnRunningHost(dcMo.findVmByNameAndLabel(templateUuidName), true);
-            DatastoreMO dsMo = null;
             Pair<VirtualMachineMO, Long> vmInfo = null;
 
             if (templateMo == null) {
@@ -378,14 +674,16 @@ public class VmwareStorageProcessor implements StorageProcessor {
             TemplateObjectTO newTemplate = new TemplateObjectTO();
 
             if (managed) {
-                if(dsMo != null) {
+                if (dsMo != null) {
                     String path = dsMo.getDatastorePath(managedStoragePoolRootVolumeName + ".vmdk");
+
                     newTemplate.setPath(path);
                 }
             }
             else {
                 newTemplate.setPath(templateUuidName);
             }
+
             newTemplate.setSize((vmInfo != null)? vmInfo.second() : new Long(0));
 
             return new CopyCmdAnswer(newTemplate);
@@ -400,11 +698,28 @@ public class VmwareStorageProcessor implements StorageProcessor {
 
             return new CopyCmdAnswer(msg);
         }
+        finally {
+            if (dsMo != null && managedStoragePoolName != null) {
+                try {
+                    removeVmfsDatastore(cmd, hyperHost, VmwareResource.getDatastoreName(managedStoragePoolName), storageHost, storagePort, trimIqn(managedStoragePoolName));
+                }
+                catch (Exception ex) {
+                    s_logger.error("Unable to remove the following datastore: " + VmwareResource.getDatastoreName(managedStoragePoolName), ex);
+                }
+            }
+        }
+    }
+
+    private boolean createVMLinkedClone(VirtualMachineMO vmTemplate, DatacenterMO dcMo, String vmdkName, ManagedObjectReference morDatastore,
+                                        ManagedObjectReference morPool) throws Exception {
+        return createVMLinkedClone(vmTemplate, dcMo, vmdkName, morDatastore, morPool, null);
     }
 
-    private boolean createVMLinkedClone(VirtualMachineMO vmTemplate, DatacenterMO dcMo, DatastoreMO dsMo, String vmdkName, ManagedObjectReference morDatastore,
-            ManagedObjectReference morPool) throws Exception {
-        ManagedObjectReference morBaseSnapshot = vmTemplate.getSnapshotMor("cloud.template.base");
+    private boolean createVMLinkedClone(VirtualMachineMO vmTemplate, DatacenterMO dcMo, String vmdkName, ManagedObjectReference morDatastore,
+                                        ManagedObjectReference morPool, ManagedObjectReference morBaseSnapshot) throws Exception {
+        if (morBaseSnapshot == null) {
+            morBaseSnapshot = vmTemplate.getSnapshotMor("cloud.template.base");
+        }
 
         if (morBaseSnapshot == null) {
             String msg = "Unable to find template base snapshot, invalid template";
@@ -428,7 +743,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     private boolean createVMFullClone(VirtualMachineMO vmTemplate, DatacenterMO dcMo, DatastoreMO dsMo, String vmdkName, ManagedObjectReference morDatastore,
-            ManagedObjectReference morPool) throws Exception {
+                                      ManagedObjectReference morPool) throws Exception {
         s_logger.info("creating full clone from template");
 
         if (!vmTemplate.createFullClone(vmdkName, dcMo.getVmFolder(), morPool, morDatastore)) {
@@ -465,7 +780,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
             DatastoreMO dsMo = new DatastoreMO(context, morDatastore);
 
             String vmdkName = volume.getName();
-            String vmdkFileBaseName = null;
+            String vmdkFileBaseName;
             if (srcStore == null) {
                 // create a root volume for blank VM (created from ISO)
                 String dummyVmName = hostService.getWorkerName(context, cmd, 0);
@@ -508,7 +823,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
                     _fullCloneFlag = volume.getSize() > template.getSize() ? true : _fullCloneFlag;
                 }
                 if (!_fullCloneFlag) {
-                    createVMLinkedClone(vmTemplate, dcMo, dsMo, vmdkName, morDatastore, morPool);
+                    createVMLinkedClone(vmTemplate, dcMo, vmdkName, morDatastore, morPool);
                 } else {
                     createVMFullClone(vmTemplate, dcMo, dsMo, vmdkName, morDatastore, morPool);
                 }
@@ -568,10 +883,10 @@ public class VmwareStorageProcessor implements StorageProcessor {
         }
     }
 
-    private Pair<String, String> copyVolumeFromSecStorage(VmwareHypervisorHost hyperHost, String srcVolumePath, DatastoreMO dsMo, String secStorageUrl, long wait, Integer nfsVersion) throws Exception {
-
-        String volumeFolder = null;
-        String volumeName = null;
+    private Pair<String, String> copyVolumeFromSecStorage(VmwareHypervisorHost hyperHost, String srcVolumePath, DatastoreMO dsMo, String secStorageUrl,
+                                                          long wait, Integer nfsVersion) throws Exception {
+        String volumeFolder;
+        String volumeName;
         String sufix = ".ova";
         int index = srcVolumePath.lastIndexOf(File.separator);
         if (srcVolumePath.endsWith(sufix)) {
@@ -585,7 +900,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
         String newVolume = VmwareHelper.getVCenterSafeUuid();
         restoreVolumeFromSecStorage(hyperHost, dsMo, newVolume, secStorageUrl, volumeFolder, volumeName, wait, nfsVersion);
 
-        return new Pair<String, String>(volumeFolder, newVolume);
+        return new Pair<>(volumeFolder, newVolume);
     }
 
     private String deleteVolumeDirOnSecondaryStorage(String volumeDir, String secStorageUrl, Integer nfsVersion) throws Exception {
@@ -652,7 +967,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     private Pair<String, String> copyVolumeToSecStorage(VmwareHostService hostService, VmwareHypervisorHost hyperHost, CopyCommand cmd, String vmName, String poolId,
-            String volumePath, String destVolumePath, String secStorageUrl, String workerVmName) throws Exception {
+                                                        String volumePath, String destVolumePath, String secStorageUrl, String workerVmName) throws Exception {
         VirtualMachineMO workerVm = null;
         VirtualMachineMO vmMo = null;
         String exportName = UUID.randomUUID().toString().replace("-", "");
@@ -688,7 +1003,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
             vmMo.createSnapshot(exportName, "Temporary snapshot for copy-volume command", false, false);
 
             exportVolumeToSecondaryStroage(vmMo, volumePath, secStorageUrl, destVolumePath, exportName, hostService.getWorkerName(hyperHost.getContext(), cmd, 1), _nfsVersion);
-            return new Pair<String, String>(destVolumePath, exportName);
+            return new Pair<>(destVolumePath, exportName);
 
         } finally {
             vmMo.removeSnapshot(exportName, false);
@@ -705,7 +1020,6 @@ public class VmwareStorageProcessor implements StorageProcessor {
         VolumeObjectTO srcVolume = (VolumeObjectTO)cmd.getSrcTO();
         VolumeObjectTO destVolume = (VolumeObjectTO)cmd.getDestTO();
         String vmName = srcVolume.getVmName();
-        String searchExcludedFolders = cmd.getContextParam("searchexludefolders");
 
         VmwareContext context = hostService.getServiceContext(cmd);
         try {
@@ -772,7 +1086,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     private Ternary<String, Long, Long> createTemplateFromVolume(VirtualMachineMO vmMo, String installPath, long templateId, String templateUniqueName,
-            String secStorageUrl, String volumePath, String workerVmName, Integer nfsVersion) throws Exception {
+                                                                 String secStorageUrl, String volumePath, String workerVmName, Integer nfsVersion) throws Exception {
 
         String secondaryMountPoint = mountService.getMountPoint(secStorageUrl, nfsVersion);
         String installFullPath = secondaryMountPoint + "/" + installPath;
@@ -827,7 +1141,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
             long physicalSize = new File(installFullPath + "/" + templateVMDKName).length();
             OVAProcessor processor = new OVAProcessor();
 
-            Map<String, Object> params = new HashMap<String, Object>();
+            Map<String, Object> params = new HashMap<>();
             params.put(StorageLayer.InstanceConfigKey, _storage);
             processor.configure("OVA Processor", params);
             long virtualSize = processor.getTemplateVirtualSize(installFullPath, templateUniqueName);
@@ -937,7 +1251,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     private Ternary<String, Long, Long> createTemplateFromSnapshot(String installPath, String templateUniqueName, String secStorageUrl, String snapshotPath,
-            Long templateId, long wait, Integer nfsVersion) throws Exception {
+                                                                   Long templateId, long wait, Integer nfsVersion) throws Exception {
         //Snapshot path is decoded in this form: /snapshots/account/volumeId/uuid/uuid
         String backupSSUuid;
         String snapshotFolder;
@@ -1050,154 +1364,394 @@ public class VmwareStorageProcessor implements StorageProcessor {
                 }
             }
 
-            long physicalSize = new File(installFullPath + "/" + templateVMDKName).length();
-            OVAProcessor processor = new OVAProcessor();
-            // long physicalSize = new File(installFullPath + "/" + templateUniqueName + ".ova").length();
-            Map<String, Object> params = new HashMap<String, Object>();
-            params.put(StorageLayer.InstanceConfigKey, _storage);
-            processor.configure("OVA Processor", params);
-            long virtualSize = processor.getTemplateVirtualSize(installFullPath, templateUniqueName);
+            Size size = handleMetadataCreateTemplateFromSnapshot(installFullPath, templateVMDKName, templateId, templateUniqueName, backupSSUuid);
 
-            postCreatePrivateTemplate(installFullPath, templateId, templateUniqueName, physicalSize, virtualSize);
-            writeMetaOvaForTemplate(installFullPath, backupSSUuid + ".ovf", templateVMDKName, templateUniqueName, physicalSize);
-            return new Ternary<String, Long, Long>(installPath + "/" + templateUniqueName + ".ova", physicalSize, virtualSize);
+            return new Ternary<>(installPath + "/" + templateUniqueName + ".ova", size.getPhysicalSize(), size.getVirtualSize());
         } finally {
             // TODO, clean up left over files
         }
     }
 
-    @Override
-    public Answer createTemplateFromSnapshot(CopyCommand cmd) {
-        SnapshotObjectTO snapshot = (SnapshotObjectTO)cmd.getSrcTO();
-        TemplateObjectTO template = (TemplateObjectTO)cmd.getDestTO();
-        DataStoreTO imageStore = template.getDataStore();
-        String details;
-        String uniqeName = UUID.randomUUID().toString();
-
-        VmwareContext context = hostService.getServiceContext(cmd);
-        try {
-            if (!(imageStore instanceof NfsTO)) {
-                return new CopyCmdAnswer("Only support create template from snapshot, when the dest store is nfs");
-            }
-
-            NfsTO nfsSvr = (NfsTO)imageStore;
-            Ternary<String, Long, Long> result = createTemplateFromSnapshot(template.getPath(), uniqeName, nfsSvr.getUrl(), snapshot.getPath(), template.getId(), (long)cmd.getWait() * 1000, _nfsVersion);
+    private class Size {
+        private final long _physicalSize;
+        private final long _virtualSize;
 
-            TemplateObjectTO newTemplate = new TemplateObjectTO();
-            newTemplate.setPath(result.first());
-            newTemplate.setPhysicalSize(result.second());
-            newTemplate.setSize(result.third());
-            newTemplate.setFormat(ImageFormat.OVA);
-            newTemplate.setName(uniqeName);
-            return new CopyCmdAnswer(newTemplate);
-        } catch (Throwable e) {
-            if (e instanceof RemoteException) {
-                hostService.invalidateServiceContext(context);
-            }
+        Size(long physicalSize, long virtualSize) {
+            _physicalSize = physicalSize;
+            _virtualSize = virtualSize;
+        }
 
-            s_logger.error("Unexpecpted exception ", e);
+        long getPhysicalSize() {
+            return _physicalSize;
+        }
 
-            details = "create template from snapshot exception: " + VmwareHelper.getExceptionMessage(e);
-            return new CopyCmdAnswer(details);
+        long getVirtualSize() {
+            return _virtualSize;
         }
     }
 
-    // return Pair<String(divice bus name), String[](disk chain)>
-    private Pair<String, String[]> exportVolumeToSecondaryStroage(VirtualMachineMO vmMo, String volumePath, String secStorageUrl, String secStorageDir,
-            String exportName, String workerVmName, Integer nfsVersion) throws Exception {
+    private Size handleMetadataCreateTemplateFromSnapshot(String installFullPath, String templateVMDKName, long templateId, String templateUniqueName,
+                                                          String ovfFilename) throws Exception {
+        long physicalSize = new File(installFullPath + "/" + templateVMDKName).length();
 
-        String secondaryMountPoint = mountService.getMountPoint(secStorageUrl, nfsVersion);
-        String exportPath = secondaryMountPoint + "/" + secStorageDir + "/" + exportName;
+        OVAProcessor processor = new OVAProcessor();
 
-        synchronized (exportPath.intern()) {
-            if (!new File(exportPath).exists()) {
-                Script command = new Script(false, "mkdir", _timeout, s_logger);
-                command.add("-p");
-                command.add(exportPath);
-                if (command.execute() != null) {
-                    throw new Exception("unable to prepare snapshot backup directory");
-                }
-            }
-        }
+        Map<String, Object> params = new HashMap<>();
 
-        VirtualMachineMO clonedVm = null;
-        try {
+        params.put(StorageLayer.InstanceConfigKey, _storage);
 
-            Pair<VirtualDisk, String> volumeDeviceInfo = vmMo.getDiskDevice(volumePath);
-            if (volumeDeviceInfo == null) {
-                String msg = "Unable to find related disk device for volume. volume path: " + volumePath;
-                s_logger.error(msg);
-                throw new Exception(msg);
-            }
+        processor.configure("OVA Processor", params);
 
-            // 4 MB is the minimum requirement for VM memory in VMware
-            Pair<VirtualMachineMO, String[]> cloneResult =
-                    vmMo.cloneFromCurrentSnapshot(workerVmName, 0, 4, volumeDeviceInfo.second(), VmwareHelper.getDiskDeviceDatastore(volumeDeviceInfo.first()));
-            clonedVm = cloneResult.first();
-            String disks[] = cloneResult.second();
+        long virtualSize = processor.getTemplateVirtualSize(installFullPath, templateUniqueName);
 
-            clonedVm.exportVm(exportPath, exportName, false, false);
-            return new Pair<String, String[]>(volumeDeviceInfo.second(), disks);
-        } finally {
-            if (clonedVm != null) {
-                clonedVm.detachAllDisks();
-                clonedVm.destroy();
-            }
-        }
-    }
+        postCreatePrivateTemplate(installFullPath, templateId, templateUniqueName, physicalSize, virtualSize);
 
-    // Ternary<String(backup uuid in secondary storage), String(device bus name), String[](original disk chain in the snapshot)>
-    private Ternary<String, String, String[]> backupSnapshotToSecondaryStorage(VirtualMachineMO vmMo, String installPath, String volumePath, String snapshotUuid,
-            String secStorageUrl, String prevSnapshotUuid, String prevBackupUuid, String workerVmName, Integer nfsVersion) throws Exception {
+        writeMetaOvaForTemplate(installFullPath, ovfFilename + ".ovf", templateVMDKName, templateUniqueName, physicalSize);
 
-        String backupUuid = UUID.randomUUID().toString();
-        Pair<String, String[]> snapshotInfo = exportVolumeToSecondaryStroage(vmMo, volumePath, secStorageUrl, installPath, backupUuid, workerVmName, nfsVersion);
-        return new Ternary<String, String, String[]>(backupUuid, snapshotInfo.first(), snapshotInfo.second());
+        return new Size(physicalSize, virtualSize);
     }
 
-    @Override
-    public Answer backupSnapshot(CopyCommand cmd) {
-        SnapshotObjectTO srcSnapshot = (SnapshotObjectTO)cmd.getSrcTO();
-        DataStoreTO primaryStore = srcSnapshot.getDataStore();
-        SnapshotObjectTO destSnapshot = (SnapshotObjectTO)cmd.getDestTO();
-        DataStoreTO destStore = destSnapshot.getDataStore();
-        if (!(destStore instanceof NfsTO)) {
-            return new CopyCmdAnswer("unsupported protocol");
-        }
+    private void setUpManagedStorageCopyTemplateFromSnapshot(CopyCommand cmd) throws Exception {
+        VmwareContext context = hostService.getServiceContext(cmd);
+        VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd);
 
-        NfsTO destNfsStore = (NfsTO)destStore;
+        ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
+        ClusterMO clusterMO = new ClusterMO(context, morCluster);
 
-        String secondaryStorageUrl = destNfsStore.getUrl();
-        String snapshotUuid = srcSnapshot.getPath();
-        String prevSnapshotUuid = srcSnapshot.getParentSnapshotPath();
-        String prevBackupUuid = destSnapshot.getParentSnapshotPath();
-        VirtualMachineMO workerVm = null;
-        String workerVMName = null;
-        String volumePath = srcSnapshot.getVolume().getPath();
-        ManagedObjectReference morDs = null;
-        DatastoreMO dsMo = null;
+        List<Pair<ManagedObjectReference, String>> lstHosts = clusterMO.getClusterHosts();
 
-        // By default assume failure
-        String details = null;
-        boolean success = false;
-        String snapshotBackupUuid = null;
+        final Map<String, String> options = cmd.getOptions();
 
-        boolean hasOwnerVm = false;
-        Ternary<String, String, String[]> backupResult = null;
+        final String storageHost = options.get(DiskTO.STORAGE_HOST);
+        final int storagePortNumber = Integer.parseInt(options.get(DiskTO.STORAGE_PORT));
+        final String iScsiName = options.get(DiskTO.IQN);
+        final String snapshotPath = options.get(DiskTO.VMDK);
+        final String chapInitiatorUsername = options.get(DiskTO.CHAP_INITIATOR_USERNAME);
+        final String chapInitiatorSecret = options.get(DiskTO.CHAP_INITIATOR_SECRET);
+        final String chapTargetUsername = options.get(DiskTO.CHAP_TARGET_USERNAME);
+        final String chapTargetSecret = options.get(DiskTO.CHAP_TARGET_SECRET);
 
-        VmwareContext context = hostService.getServiceContext(cmd);
-        VirtualMachineMO vmMo = null;
-        String vmName = srcSnapshot.getVmName();
-        try {
-            VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd);
-            morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, primaryStore.getUuid());
+        String datastoreName = getManagedDatastoreNameFromPath(snapshotPath);
 
-            CopyCmdAnswer answer = null;
+        HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(context, storageHost, lstHosts);
+        List<HostMO> hostsUsingStaticDiscovery = hostDiscoveryMethod.getHostsUsingStaticDiscovery();
 
-            try {
-                if(vmName != null) {
-                    vmMo = hyperHost.findVmOnHyperHost(vmName);
-                    if (vmMo == null) {
+        if (hostsUsingStaticDiscovery != null && hostsUsingStaticDiscovery.size() > 0) {
+            final List<HostInternetScsiHbaStaticTarget> lstTargets = getTargets(storageHost, storagePortNumber, trimIqn(iScsiName),
+                    chapInitiatorUsername, chapInitiatorSecret, chapTargetUsername, chapTargetSecret);
+
+            addRemoveInternetScsiTargetsToAllHosts(true, lstTargets, hostsUsingStaticDiscovery);
+        }
+
+        rescanAllHosts(context, lstHosts, true, true);
+
+        Pair<ManagedObjectReference, String> firstHost = lstHosts.get(0);
+        HostMO firstHostMO = new HostMO(context, firstHost.first());
+        HostDatastoreSystemMO firstHostDatastoreSystemMO = firstHostMO.getHostDatastoreSystemMO();
+        ManagedObjectReference morDs = firstHostDatastoreSystemMO.findDatastoreByName(datastoreName);
+        DatastoreMO datastoreMO = new DatastoreMO(context, morDs);
+
+        mountVmfsDatastore(datastoreMO, lstHosts);
+    }
+
+    private void takeDownManagedStorageCopyTemplateFromSnapshot(CopyCommand cmd) throws Exception {
+        VmwareContext context = hostService.getServiceContext(cmd);
+        VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd);
+
+        ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
+        ClusterMO clusterMO = new ClusterMO(context, morCluster);
+
+        List<Pair<ManagedObjectReference, String>> lstHosts = clusterMO.getClusterHosts();
+
+        final Map<String, String> options = cmd.getOptions();
+
+        final String storageHost = options.get(DiskTO.STORAGE_HOST);
+        final int storagePortNumber = Integer.parseInt(options.get(DiskTO.STORAGE_PORT));
+        final String iScsiName = options.get(DiskTO.IQN);
+        final String snapshotPath = options.get(DiskTO.VMDK);
+
+        String datastoreName = getManagedDatastoreNameFromPath(snapshotPath);
+
+        unmountVmfsDatastore(context, hyperHost, datastoreName, lstHosts);
+
+        HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(context, storageHost, lstHosts);
+        List<HostMO> hostsUsingStaticDiscovery = hostDiscoveryMethod.getHostsUsingStaticDiscovery();
+
+        if (hostsUsingStaticDiscovery != null && hostsUsingStaticDiscovery.size() > 0) {
+            final List<HostInternetScsiHbaStaticTarget> lstTargets = getTargets(storageHost, storagePortNumber, trimIqn(iScsiName),
+                    null, null, null, null);
+
+            addRemoveInternetScsiTargetsToAllHosts(false, lstTargets, hostsUsingStaticDiscovery);
+
+            rescanAllHosts(context, lstHosts, true, false);
+        }
+    }
+
+    private void createTemplateFolder(String installPath, String installFullPath, NfsTO nfsSvr) {
+        synchronized (installPath.intern()) {
+            Script command = new Script(false, "mkdir", _timeout, s_logger);
+
+            command.add("-p");
+            command.add(installFullPath);
+
+            String result = command.execute();
+
+            if (result != null) {
+                String secStorageUrl = nfsSvr.getUrl();
+                String msg = "unable to prepare template directory: " + installPath + "; storage: " + secStorageUrl + "; error msg: " + result;
+
+                s_logger.error(msg);
+
+                throw new CloudRuntimeException(msg);
+            }
+        }
+    }
+
+    private void exportManagedStorageSnapshotToTemplate(CopyCommand cmd, String installFullPath, String snapshotPath, String exportName) throws Exception {
+        DatastoreFile dsFile = new DatastoreFile(snapshotPath);
+
+        VmwareContext context = hostService.getServiceContext(cmd);
+        VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
+
+        String workerVMName = hostService.getWorkerName(context, cmd, 0);
+
+        ManagedObjectReference dsMor = hyperHost.findDatastoreByName(dsFile.getDatastoreName());
+        DatastoreMO dsMo = new DatastoreMO(context, dsMor);
+
+        VirtualMachineMO workerVM = HypervisorHostHelper.createWorkerVM(hyperHost, dsMo, workerVMName);
+
+        if (workerVM == null) {
+            throw new CloudRuntimeException("Failed to find the newly created worker VM: " + workerVMName);
+        }
+
+        workerVM.attachDisk(new String[]{snapshotPath}, dsMor);
+
+        workerVM.exportVm(installFullPath, exportName, false, false);
+
+        workerVM.detachAllDisks();
+        workerVM.destroy();
+    }
+
+    private String getTemplateVmdkName(String installFullPath, String exportName) {
+        File templateDir = new File(installFullPath);
+        File[] templateFiles = templateDir.listFiles();
+
+        if (templateFiles == null) {
+            String msg = "Unable to find template files in " + installFullPath;
+
+            s_logger.error(msg);
+
+            throw new CloudRuntimeException(msg);
+        }
+
+        for (int i = 0; i < templateFiles.length; i++) {
+            String templateFile = templateFiles[i].getName();
+
+            if (templateFile.toLowerCase().startsWith(exportName) && templateFile.toLowerCase().endsWith(".vmdk")) {
+                return templateFile;
+            }
+        }
+
+        throw new CloudRuntimeException("Unable to locate the template VMDK file");
+    }
+
+    private Answer handleManagedStorageCreateTemplateFromSnapshot(CopyCommand cmd, TemplateObjectTO template, NfsTO nfsSvr) {
+        try {
+            setUpManagedStorageCopyTemplateFromSnapshot(cmd);
+
+            final Map<String, String> options = cmd.getOptions();
+
+            String snapshotPath = options.get(DiskTO.VMDK);
+
+            String secondaryMountPoint = mountService.getMountPoint(nfsSvr.getUrl(), _nfsVersion);
+            String installPath = template.getPath();
+            String installFullPath = secondaryMountPoint + "/" + installPath;
+
+            createTemplateFolder(installPath, installFullPath, nfsSvr);
+
+            String exportName = UUID.randomUUID().toString();
+
+            exportManagedStorageSnapshotToTemplate(cmd, installFullPath, snapshotPath, exportName);
+
+            String templateVmdkName = getTemplateVmdkName(installFullPath, exportName);
+
+            String uniqueName = options.get(DiskTO.UUID);
+
+            Size size = handleMetadataCreateTemplateFromSnapshot(installFullPath, templateVmdkName, template.getId(), uniqueName, exportName);
+
+            TemplateObjectTO newTemplate = new TemplateObjectTO();
+
+            newTemplate.setPath(installPath + "/" + uniqueName + ".ova");
+            newTemplate.setPhysicalSize(size.getPhysicalSize());
+            newTemplate.setSize(size.getVirtualSize());
+            newTemplate.setFormat(ImageFormat.OVA);
+            newTemplate.setName(uniqueName);
+
+            return new CopyCmdAnswer(newTemplate);
+        }
+        catch (Exception ex) {
+            String errMsg = "Problem creating a template from a snapshot for managed storage: " + ex.getMessage();
+
+            s_logger.error(errMsg);
+
+            throw new CloudRuntimeException(errMsg, ex);
+        }
+        finally {
+            try {
+                takeDownManagedStorageCopyTemplateFromSnapshot(cmd);
+            }
+            catch (Exception ex) {
+                s_logger.warn("Unable to remove one or more static targets");
+            }
+        }
+    }
+
+    @Override
+    public Answer createTemplateFromSnapshot(CopyCommand cmd) {
+        String details;
+
+        SnapshotObjectTO snapshot = (SnapshotObjectTO)cmd.getSrcTO();
+        TemplateObjectTO template = (TemplateObjectTO)cmd.getDestTO();
+
+        DataStoreTO imageStore = template.getDataStore();
+
+        String uniqueName = UUID.randomUUID().toString();
+
+        VmwareContext context = hostService.getServiceContext(cmd);
+
+        try {
+            if (!(imageStore instanceof NfsTO)) {
+                return new CopyCmdAnswer("Creating a template from a snapshot is only supported when the destination store is NFS.");
+            }
+
+            NfsTO nfsSvr = (NfsTO)imageStore;
+
+            if (snapshot.getDataStore() instanceof PrimaryDataStoreTO && template.getDataStore() instanceof NfsTO) {
+                return handleManagedStorageCreateTemplateFromSnapshot(cmd, template, nfsSvr);
+            }
+
+            Ternary<String, Long, Long> result = createTemplateFromSnapshot(template.getPath(), uniqueName, nfsSvr.getUrl(), snapshot.getPath(), template.getId(),
+                    cmd.getWait() * 1000, _nfsVersion);
+
+            TemplateObjectTO newTemplate = new TemplateObjectTO();
+
+            newTemplate.setPath(result.first());
+            newTemplate.setPhysicalSize(result.second());
+            newTemplate.setSize(result.third());
+            newTemplate.setFormat(ImageFormat.OVA);
+            newTemplate.setName(uniqueName);
+
+            return new CopyCmdAnswer(newTemplate);
+        } catch (Throwable e) {
+            if (e instanceof RemoteException) {
+                hostService.invalidateServiceContext(context);
+            }
+
+            s_logger.error("Unexpected exception ", e);
+
+            details = "create template from snapshot exception: " + VmwareHelper.getExceptionMessage(e);
+
+            return new CopyCmdAnswer(details);
+        }
+    }
+
+    // return Pair<String(divice bus name), String[](disk chain)>
+    private Pair<String, String[]> exportVolumeToSecondaryStroage(VirtualMachineMO vmMo, String volumePath, String secStorageUrl, String secStorageDir,
+                                                                  String exportName, String workerVmName, Integer nfsVersion) throws Exception {
+
+        String secondaryMountPoint = mountService.getMountPoint(secStorageUrl, nfsVersion);
+        String exportPath = secondaryMountPoint + "/" + secStorageDir + "/" + exportName;
+
+        synchronized (exportPath.intern()) {
+            if (!new File(exportPath).exists()) {
+                Script command = new Script(false, "mkdir", _timeout, s_logger);
+                command.add("-p");
+                command.add(exportPath);
+                if (command.execute() != null) {
+                    throw new Exception("unable to prepare snapshot backup directory");
+                }
+            }
+        }
+
+        VirtualMachineMO clonedVm = null;
+        try {
+
+            Pair<VirtualDisk, String> volumeDeviceInfo = vmMo.getDiskDevice(volumePath);
+            if (volumeDeviceInfo == null) {
+                String msg = "Unable to find related disk device for volume. volume path: " + volumePath;
+                s_logger.error(msg);
+                throw new Exception(msg);
+            }
+
+            // 4 MB is the minimum requirement for VM memory in VMware
+            Pair<VirtualMachineMO, String[]> cloneResult =
+                    vmMo.cloneFromCurrentSnapshot(workerVmName, 0, 4, volumeDeviceInfo.second(), VmwareHelper.getDiskDeviceDatastore(volumeDeviceInfo.first()));
+            clonedVm = cloneResult.first();
+            String disks[] = cloneResult.second();
+
+            clonedVm.exportVm(exportPath, exportName, false, false);
+            return new Pair<>(volumeDeviceInfo.second(), disks);
+        } finally {
+            if (clonedVm != null) {
+                clonedVm.detachAllDisks();
+                clonedVm.destroy();
+            }
+        }
+    }
+
+    // Ternary<String(backup uuid in secondary storage), String(device bus name), String[](original disk chain in the snapshot)>
+    private Ternary<String, String, String[]> backupSnapshotToSecondaryStorage(VirtualMachineMO vmMo, String installPath, String volumePath, String snapshotUuid,
+                                                                               String secStorageUrl, String prevSnapshotUuid, String prevBackupUuid, String workerVmName,
+                                                                               Integer nfsVersion) throws Exception {
+
+        String backupUuid = UUID.randomUUID().toString();
+        Pair<String, String[]> snapshotInfo = exportVolumeToSecondaryStroage(vmMo, volumePath, secStorageUrl, installPath, backupUuid, workerVmName, nfsVersion);
+        return new Ternary<>(backupUuid, snapshotInfo.first(), snapshotInfo.second());
+    }
+
+    @Override
+    public Answer backupSnapshot(CopyCommand cmd) {
+        SnapshotObjectTO srcSnapshot = (SnapshotObjectTO)cmd.getSrcTO();
+        DataStoreTO primaryStore = srcSnapshot.getDataStore();
+        SnapshotObjectTO destSnapshot = (SnapshotObjectTO)cmd.getDestTO();
+        DataStoreTO destStore = destSnapshot.getDataStore();
+        if (!(destStore instanceof NfsTO)) {
+            return new CopyCmdAnswer("unsupported protocol");
+        }
+
+        NfsTO destNfsStore = (NfsTO)destStore;
+
+        String secondaryStorageUrl = destNfsStore.getUrl();
+        String snapshotUuid = srcSnapshot.getPath();
+        String prevSnapshotUuid = srcSnapshot.getParentSnapshotPath();
+        String prevBackupUuid = destSnapshot.getParentSnapshotPath();
+        VirtualMachineMO workerVm = null;
+        String workerVMName = null;
+        String volumePath = srcSnapshot.getVolume().getPath();
+        ManagedObjectReference morDs;
+        DatastoreMO dsMo;
+
+        // By default assume failure
+        String details;
+        boolean success;
+        String snapshotBackupUuid;
+
+        boolean hasOwnerVm = false;
+        Ternary<String, String, String[]> backupResult = null;
+
+        VmwareContext context = hostService.getServiceContext(cmd);
+        VirtualMachineMO vmMo = null;
+        String vmName = srcSnapshot.getVmName();
+        try {
+            VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd);
+            morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, primaryStore.getUuid());
+
+            CopyCmdAnswer answer = null;
+
+            try {
+                if(vmName != null) {
+                    vmMo = hyperHost.findVmOnHyperHost(vmName);
+                    if (vmMo == null) {
                         if(s_logger.isDebugEnabled()) {
                             s_logger.debug("Unable to find owner VM for BackupSnapshotCommand on host " + hyperHost.getHyperHostName() + ", will try within datacenter");
                         }
@@ -1237,7 +1791,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
                     details = "Successfully backedUp the snapshot with Uuid: " + snapshotUuid + " to secondary storage.";
 
                     // Get snapshot physical size
-                    long physicalSize = 0l;
+                    long physicalSize = 0;
                     String secondaryMountPoint = mountService.getMountPoint(secondaryStorageUrl, _nfsVersion);
                     String snapshotDir =  destSnapshot.getPath() + "/" + snapshotBackupUuid;
                     File[] files = new File(secondaryMountPoint + "/" + snapshotDir).listFiles();
@@ -1350,27 +1904,39 @@ public class VmwareStorageProcessor implements StorageProcessor {
         return this.attachVolume(cmd, cmd.getDisk(), true, isManaged, cmd.getVmName(), iScsiName, storageHost, storagePort, cmd.getControllerInfo());
     }
 
-    private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean isManaged, String vmName, String iScsiName, String storageHost, int storagePort, Map<String, String> controllerInfo) {
+    private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean isManaged, String vmName, String iScsiName,
+                                String storageHost, int storagePort, Map<String, String> controllerInfo) {
         VolumeObjectTO volumeTO = (VolumeObjectTO)disk.getData();
         DataStoreTO primaryStore = volumeTO.getDataStore();
+
+        String vmdkPath = isManaged ? resource.getVmdkPath(volumeTO.getPath()) : null;
+
         try {
             VmwareContext context = hostService.getServiceContext(null);
             VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
             VirtualMachineMO vmMo = hyperHost.findVmOnHyperHost(vmName);
+
             if (vmMo == null) {
-                String msg = "Unable to find the VM to execute AttachCommand, vmName: " + vmName;
-                s_logger.error(msg);
-                throw new Exception(msg);
+                vmMo = hyperHost.findVmOnPeerHyperHost(vmName);
+
+                if (vmMo == null) {
+                    String msg = "Unable to find the VM to execute AttachCommand, vmName: " + vmName;
+
+                    s_logger.error(msg);
+
+                    throw new Exception(msg);
+                }
             }
+
             vmName = vmMo.getName();
 
-            ManagedObjectReference morDs = null;
+            ManagedObjectReference morDs;
             String diskUuid =  volumeTO.getUuid().replace("-", "");
 
             if (isAttach && isManaged) {
                 Map<String, String> details = disk.getDetails();
 
-                morDs = prepareManagedStorage(context, hyperHost, diskUuid, iScsiName, storageHost, storagePort, null,
+                morDs = prepareManagedStorage(context, hyperHost, diskUuid, iScsiName, storageHost, storagePort, vmdkPath,
                             details.get(DiskTO.CHAP_INITIATOR_USERNAME), details.get(DiskTO.CHAP_INITIATOR_SECRET),
                             details.get(DiskTO.CHAP_TARGET_USERNAME), details.get(DiskTO.CHAP_TARGET_SECRET),
                             volumeTO.getSize(), cmd);
@@ -1394,13 +1960,13 @@ public class VmwareStorageProcessor implements StorageProcessor {
 
             if (isAttach) {
                 if (isManaged) {
-                    datastoreVolumePath = dsMo.getDatastorePath(dsMo.getName() + ".vmdk");
+                    datastoreVolumePath = dsMo.getDatastorePath((vmdkPath != null ? vmdkPath : dsMo.getName()) + ".vmdk");
                 } else {
                     datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumeTO.getPath(), VmwareManager.s_vmwareSearchExcludeFolder.value());
                 }
             } else {
                 if (isManaged) {
-                    datastoreVolumePath = dsMo.getDatastorePath(dsMo.getName() + ".vmdk");
+                    datastoreVolumePath = dsMo.getDatastorePath((vmdkPath != null ? vmdkPath : dsMo.getName()) + ".vmdk");
                 } else {
                     datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumeTO.getPath() + ".vmdk");
 
@@ -1416,20 +1982,26 @@ public class VmwareStorageProcessor implements StorageProcessor {
 
             if (isAttach) {
                 String diskController = getLegacyVmDataDiskController();
-                if (controllerInfo != null &&
-                        !Strings.isNullOrEmpty(controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER))) {
+
+                if (controllerInfo != null && !Strings.isNullOrEmpty(controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER))) {
                     diskController = controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER);
                 }
+
                 if (DiskControllerType.getType(diskController) == DiskControllerType.osdefault) {
                     diskController = vmMo.getRecommendedDiskController(null);
                 }
-                vmMo.attachDisk(new String[] {datastoreVolumePath}, morDs, diskController);
+
+                vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs, diskController);
+
+                if (isManaged) {
+                    expandVirtualDisk(vmMo, datastoreVolumePath, volumeTO.getSize());
+                }
             } else {
                 vmMo.removeAllSnapshots();
                 vmMo.detachDisk(datastoreVolumePath, false);
 
                 if (isManaged) {
-                    handleDatastoreAndVmdkDetachManaged(diskUuid, iScsiName, storageHost, storagePort);
+                    handleDatastoreAndVmdkDetachManaged(cmd, diskUuid, iScsiName, storageHost, storagePort);
                 } else {
                     VmwareStorageLayoutHelper.syncVolumeToRootFolder(dsMo.getOwnerDatacenter().first(), dsMo, volumeTO.getPath(), vmName, VmwareManager.s_vmwareSearchExcludeFolder.value());
                 }
@@ -1439,19 +2011,66 @@ public class VmwareStorageProcessor implements StorageProcessor {
         } catch (Throwable e) {
             if (e instanceof RemoteException) {
                 s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context");
+
                 hostService.invalidateServiceContext(null);
             }
 
             String msg = "";
-            if (isAttach)
+
+            if (isAttach) {
                 msg += "Failed to attach volume: " + e.getMessage();
-            else
+            }
+            else {
                 msg += "Failed to detach volume: " + e.getMessage();
+            }
+
             s_logger.error(msg, e);
+
             return new AttachAnswer(msg);
         }
     }
 
+    private boolean expandVirtualDisk(VirtualMachineMO vmMo, String datastoreVolumePath, long currentSizeInBytes) throws Exception {
+        long currentSizeInKB = currentSizeInBytes / 1024;
+
+        Pair<VirtualDisk, String> vDiskPair = vmMo.getDiskDevice(datastoreVolumePath);
+
+        VirtualDisk vDisk = vDiskPair.first();
+
+        if (vDisk.getCapacityInKB() < currentSizeInKB) {
+            // IDE virtual disk cannot be re-sized if VM is running
+            if (vDiskPair.second() != null && vDiskPair.second().contains("ide")) {
+                throw new Exception("Re-sizing a virtual disk over an IDE controller is not supported in VMware hypervisor. " +
+                        "Please re-try when virtual disk is attached to a VM using a SCSI controller.");
+            }
+
+            String vmdkAbsFile = resource.getAbsoluteVmdkFile(vDisk);
+
+            if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
+                vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
+            }
+
+            vDisk.setCapacityInKB(currentSizeInKB);
+
+            VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
+
+            deviceConfigSpec.setDevice(vDisk);
+            deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
+
+            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
+
+            vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
+
+            if (!vmMo.configureVm(vmConfigSpec)) {
+                throw new Exception("Failed to configure VM to resize disk. vmName: " + vmMo.getName());
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
     private static String getSecondaryDatastoreUUID(String storeUrl) {
         String uuid = null;
         try{
@@ -1462,7 +2081,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
         return uuid;
     }
 
-    public synchronized ManagedObjectReference prepareSecondaryDatastoreOnHost(String storeUrl) throws Exception {
+    private synchronized ManagedObjectReference prepareSecondaryDatastoreOnHost(String storeUrl) throws Exception {
         String storeName = getSecondaryDatastoreUUID(storeUrl);
         URI uri = new URI(storeUrl);
 
@@ -1607,7 +2226,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
                     }
                     catch (Exception e) {
                         s_logger.error("Deleting file " + volumeDatastorePath + " due to error: " + e.getMessage());
-                        VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, volumeUuid.toString(), dcMo, VmwareManager.s_vmwareSearchExcludeFolder.value());
+                        VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, volumeUuid, dcMo, VmwareManager.s_vmwareSearchExcludeFolder.value());
                         throw new CloudRuntimeException("Unable to create volume due to: " + e.getMessage());
                     }
                 }
@@ -1702,6 +2321,9 @@ public class VmwareStorageProcessor implements StorageProcessor {
                         DatacenterMO dcMo = new DatacenterMO(context, morDc);
                         vmMo = dcMo.findVm(vmName);
                     }
+
+                    List<Map<String, String>> dynamicTargetsToRemove = null;
+
                     if (vmMo != null) {
                         if (s_logger.isInfoEnabled()) {
                             s_logger.info("Destroy root volume and VM itself. vmName " + vmName);
@@ -1725,7 +2347,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
                         // don't remove the iSCSI connection(s) until the supported disk(s) is/are removed from the VM
                         // (removeManagedTargetsFromCluster should be called after detachAllDisksExcept and vm.destroy)
                         List<VirtualDisk> virtualDisks = vmMo.getVirtualDisks();
-                        List<String> managedIqns = getManagedIqnsFromVirtualDisks(virtualDisks);
+                        List<String> managedDatastoreNames = getManagedDatastoreNamesFromVirtualDisks(virtualDisks);
 
                         List<String> detachedDisks = vmMo.detachAllDisksExcept(vol.getPath(), diskInfo != null ? diskInfo.getDiskDeviceBusName() : null);
                         VmwareStorageLayoutHelper.moveVolumeToRootFolder(new DatacenterMO(context, morDc), detachedDisks);
@@ -1741,8 +2363,8 @@ public class VmwareStorageProcessor implements StorageProcessor {
                         }
 
                         // this.hostService.handleDatastoreAndVmdkDetach(iScsiName, storageHost, storagePort);
-                        if (managedIqns != null && !managedIqns.isEmpty()) {
-                            removeManagedTargetsFromCluster(managedIqns);
+                        if (managedDatastoreNames != null && !managedDatastoreNames.isEmpty()) {
+                            removeManagedTargetsFromCluster(managedDatastoreNames);
                         }
 
                         for (NetworkDetails netDetails : networks) {
@@ -1761,7 +2383,8 @@ public class VmwareStorageProcessor implements StorageProcessor {
 
                     VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vol.getPath(), new DatacenterMO(context, morDc));
                      */
-                    return new Answer(cmd, true, "Success");
+
+                    return new Answer(cmd, true, "");
                 }
 
                 if (s_logger.isInfoEnabled()) {
@@ -1769,7 +2392,9 @@ public class VmwareStorageProcessor implements StorageProcessor {
                 }
             }
 
-            VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vol.getPath(), new DatacenterMO(context, morDc), VmwareManager.s_vmwareSearchExcludeFolder.value());
+            if (!isManaged) {
+                VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vol.getPath(), new DatacenterMO(context, morDc), VmwareManager.s_vmwareSearchExcludeFolder.value());
+            }
 
             return new Answer(cmd, true, "Success");
         } catch (Throwable e) {
@@ -1785,16 +2410,16 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     public ManagedObjectReference prepareManagedDatastore(VmwareContext context, VmwareHypervisorHost hyperHost, String datastoreName,
-            String iScsiName, String storageHost, int storagePort) throws Exception {
+                                                          String iScsiName, String storageHost, int storagePort) throws Exception {
         return getVmfsDatastore(context, hyperHost, datastoreName, storageHost, storagePort, trimIqn(iScsiName), null, null, null, null);
     }
 
     private ManagedObjectReference prepareManagedDatastore(VmwareContext context, VmwareHypervisorHost hyperHost, String diskUuid, String iScsiName,
-                String storageHost, int storagePort, String chapInitiatorUsername, String chapInitiatorSecret,
-                String chapTargetUsername, String chapTargetSecret) throws Exception {
+                                                           String storageHost, int storagePort, String chapInitiatorUsername, String chapInitiatorSecret,
+                                                           String chapTargetUsername, String chapTargetSecret) throws Exception {
         if (storagePort == DEFAULT_NFS_PORT) {
             s_logger.info("creating the NFS datastore with the following configuration - storageHost: " + storageHost + ", storagePort: " + storagePort +
-                          ", exportpath: " + iScsiName + "and diskUuid : " + diskUuid);
+                    ", exportpath: " + iScsiName + "and diskUuid : " + diskUuid);
             ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
             ClusterMO cluster = new ClusterMO(context, morCluster);
             List<Pair<ManagedObjectReference, String>> lstHosts = cluster.getClusterHosts();
@@ -1802,85 +2427,173 @@ public class VmwareStorageProcessor implements StorageProcessor {
             HostMO host = new HostMO(context, lstHosts.get(0).first());
             HostDatastoreSystemMO hostDatastoreSystem = host.getHostDatastoreSystemMO();
 
-            return hostDatastoreSystem.createNfsDatastore(storageHost, storagePort, iScsiName, diskUuid);
-         } else {
-             return getVmfsDatastore(context, hyperHost, VmwareResource.getDatastoreName(iScsiName), storageHost, storagePort,
-                        trimIqn(iScsiName), chapInitiatorUsername, chapInitiatorSecret, chapTargetUsername, chapTargetSecret);
-         }
+            return hostDatastoreSystem.createNfsDatastore(storageHost, storagePort, iScsiName, diskUuid);
+        } else {
+            return getVmfsDatastore(context, hyperHost, VmwareResource.getDatastoreName(iScsiName), storageHost, storagePort,
+                    trimIqn(iScsiName), chapInitiatorUsername, chapInitiatorSecret, chapTargetUsername, chapTargetSecret);
+        }
+    }
+
+    private List<HostInternetScsiHbaStaticTarget> getTargets(String storageIpAddress, int storagePortNumber, String iqn,
+                                                             String chapName, String chapSecret, String mutualChapName, String mutualChapSecret) {
+        HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget();
+
+        target.setAddress(storageIpAddress);
+        target.setPort(storagePortNumber);
+        target.setIScsiName(iqn);
+
+        if (StringUtils.isNotBlank(chapName) && StringUtils.isNotBlank(chapSecret)) {
+            HostInternetScsiHbaAuthenticationProperties auth = new HostInternetScsiHbaAuthenticationProperties();
+
+            String strAuthType = "chapRequired";
+
+            auth.setChapAuthEnabled(true);
+            auth.setChapInherited(false);
+            auth.setChapAuthenticationType(strAuthType);
+            auth.setChapName(chapName);
+            auth.setChapSecret(chapSecret);
+
+            if (StringUtils.isNotBlank(mutualChapName) && StringUtils.isNotBlank(mutualChapSecret)) {
+                auth.setMutualChapInherited(false);
+                auth.setMutualChapAuthenticationType(strAuthType);
+                auth.setMutualChapName(mutualChapName);
+                auth.setMutualChapSecret(mutualChapSecret);
+            }
+
+            target.setAuthenticationProperties(auth);
+        }
+
+        final List<HostInternetScsiHbaStaticTarget> lstTargets = new ArrayList<>();
+
+        lstTargets.add(target);
+
+        return lstTargets;
+    }
+
+    private class HostDiscoveryMethod {
+        private final List<HostMO> hostsUsingDynamicDiscovery;
+        private final List<HostMO> hostsUsingStaticDiscovery;
+
+        HostDiscoveryMethod(List<HostMO> hostsUsingDynamicDiscovery, List<HostMO> hostsUsingStaticDiscovery) {
+            this.hostsUsingDynamicDiscovery = hostsUsingDynamicDiscovery;
+            this.hostsUsingStaticDiscovery = hostsUsingStaticDiscovery;
+        }
+
+        List<HostMO> getHostsUsingDynamicDiscovery() {
+            return hostsUsingDynamicDiscovery;
+        }
+
+        List<HostMO> getHostsUsingStaticDiscovery() {
+            return hostsUsingStaticDiscovery;
+        }
+    }
+
+    private HostDiscoveryMethod getHostDiscoveryMethod(VmwareContext context, String address,
+                                                       List<Pair<ManagedObjectReference, String>> hostPairs) throws Exception {
+        List<HostMO> hosts = new ArrayList<>();
+
+        for (Pair<ManagedObjectReference, String> hostPair : hostPairs) {
+            HostMO host = new HostMO(context, hostPair.first());
+
+            hosts.add(host);
+        }
+
+        return getHostDiscoveryMethod(address, hosts);
     }
 
-    private ManagedObjectReference getVmfsDatastore(VmwareContext context, VmwareHypervisorHost hyperHost, String datastoreName, String storageIpAddress, int storagePortNumber,
-            String iqn, String chapName, String chapSecret, String mutualChapName, String mutualChapSecret) throws Exception {
-        ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
-        ClusterMO cluster = new ClusterMO(context, morCluster);
-        List<Pair<ManagedObjectReference, String>> lstHosts = cluster.getClusterHosts();
+    private HostDiscoveryMethod getHostDiscoveryMethod(String address, List<HostMO> lstHosts) throws Exception {
+        List<HostMO> hostsUsingDynamicDiscovery = new ArrayList<>();
+        List<HostMO> hostsUsingStaticDiscovery = new ArrayList<>();
 
-        HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget();
+        for (HostMO host : lstHosts) {
+            boolean usingDynamicDiscovery = false;
 
-        target.setAddress(storageIpAddress);
-        target.setPort(storagePortNumber);
-        target.setIScsiName(iqn);
+            HostStorageSystemMO hostStorageSystem = host.getHostStorageSystemMO();
 
-        if (StringUtils.isNotBlank(chapName) && StringUtils.isNotBlank(chapSecret)) {
-            HostInternetScsiHbaAuthenticationProperties auth = new HostInternetScsiHbaAuthenticationProperties();
+            for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) {
+                if (hba instanceof HostInternetScsiHba) {
+                    HostInternetScsiHba hostInternetScsiHba = (HostInternetScsiHba)hba;
 
-            String strAuthType = "chapRequired";
+                    if (hostInternetScsiHba.isIsSoftwareBased()) {
+                        List<HostInternetScsiHbaSendTarget> sendTargets = hostInternetScsiHba.getConfiguredSendTarget();
 
-            auth.setChapAuthEnabled(true);
-            auth.setChapInherited(false);
-            auth.setChapAuthenticationType(strAuthType);
-            auth.setChapName(chapName);
-            auth.setChapSecret(chapSecret);
+                        if (sendTargets != null) {
+                            for (HostInternetScsiHbaSendTarget sendTarget : sendTargets) {
+                                String sendTargetAddress = sendTarget.getAddress();
 
-            if (StringUtils.isNotBlank(mutualChapName) && StringUtils.isNotBlank(mutualChapSecret)) {
-                auth.setMutualChapInherited(false);
-                auth.setMutualChapAuthenticationType(strAuthType);
-                auth.setMutualChapName(mutualChapName);
-                auth.setMutualChapSecret(mutualChapSecret);
+                                if (sendTargetAddress.contains(address)) {
+                                    usingDynamicDiscovery = true;
+                                }
+                            }
+                        }
+                    }
+                }
             }
 
-            target.setAuthenticationProperties(auth);
+            if (usingDynamicDiscovery) {
+                hostsUsingDynamicDiscovery.add(host);
+            }
+            else {
+                hostsUsingStaticDiscovery.add(host);
+            }
         }
 
-        final List<HostInternetScsiHbaStaticTarget> lstTargets = new ArrayList<HostInternetScsiHbaStaticTarget>();
+        return new HostDiscoveryMethod(hostsUsingDynamicDiscovery, hostsUsingStaticDiscovery);
+    }
 
-        lstTargets.add(target);
+    private ManagedObjectReference getVmfsDatastore(VmwareContext context, VmwareHypervisorHost hyperHost, String datastoreName, String storageIpAddress, int storagePortNumber,
+                                                    String iqn, String chapName, String chapSecret, String mutualChapName, String mutualChapSecret) throws Exception {
+        ManagedObjectReference morDs;
 
-        addRemoveInternetScsiTargetsToAllHosts(context, true, lstTargets, lstHosts);
+        ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
+        ClusterMO cluster = new ClusterMO(context, morCluster);
+        List<Pair<ManagedObjectReference, String>> lstHosts = cluster.getClusterHosts();
 
-        rescanAllHosts(context, lstHosts);
+        Pair<ManagedObjectReference, String> firstHost = lstHosts.get(0);
+        HostMO firstHostMO = new HostMO(context, firstHost.first());
+        HostDatastoreSystemMO firstHostDatastoreSystemMO = firstHostMO.getHostDatastoreSystemMO();
 
-        HostMO host = new HostMO(context, lstHosts.get(0).first());
-        HostDatastoreSystemMO hostDatastoreSystem = host.getHostDatastoreSystemMO();
+        HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(context, storageIpAddress, lstHosts);
+        List<HostMO> hostsUsingStaticDiscovery = hostDiscoveryMethod.getHostsUsingStaticDiscovery();
 
-        ManagedObjectReference morDs = hostDatastoreSystem.findDatastoreByName(datastoreName);
+        if (hostsUsingStaticDiscovery != null && hostsUsingStaticDiscovery.size() > 0) {
+            List<HostInternetScsiHbaStaticTarget> lstTargets = getTargets(storageIpAddress, storagePortNumber, iqn,
+                    chapName, chapSecret, mutualChapName, mutualChapSecret);
 
-        if (morDs != null) {
-            return morDs;
+            addRemoveInternetScsiTargetsToAllHosts(true, lstTargets, hostsUsingStaticDiscovery);
         }
 
-        rescanAllHosts(context, lstHosts);
+        rescanAllHosts(context, lstHosts, true, false);
 
-        HostStorageSystemMO hostStorageSystem = host.getHostStorageSystemMO();
-        List<HostScsiDisk> lstHostScsiDisks = hostDatastoreSystem.queryAvailableDisksForVmfs();
+        HostStorageSystemMO firstHostStorageSystem = firstHostMO.getHostStorageSystemMO();
+        List<HostScsiDisk> lstHostScsiDisks = firstHostDatastoreSystemMO.queryAvailableDisksForVmfs();
 
-        HostScsiDisk hostScsiDisk = getHostScsiDisk(hostStorageSystem.getStorageDeviceInfo().getScsiTopology(), lstHostScsiDisks, iqn);
+        HostScsiDisk hostScsiDisk = getHostScsiDisk(firstHostStorageSystem.getStorageDeviceInfo().getScsiTopology(), lstHostScsiDisks, iqn);
 
         if (hostScsiDisk == null) {
-            // check to see if the datastore actually does exist already
-            morDs = hostDatastoreSystem.findDatastoreByName(datastoreName);
+            rescanAllHosts(context, lstHosts, false, true);
+
+            morDs = firstHostDatastoreSystemMO.findDatastoreByName(datastoreName);
 
             if (morDs != null) {
+                waitForAllHostsToSeeDatastore(lstHosts, new DatastoreMO(context, morDs));
+
+                mountVmfsDatastore(new DatastoreMO(context, morDs), lstHosts);
+
+                expandDatastore(firstHostDatastoreSystemMO, new DatastoreMO(context, morDs));
+
                 return morDs;
             }
 
             throw new Exception("A relevant SCSI disk could not be located to use to create a datastore.");
         }
 
-        morDs = hostDatastoreSystem.createVmfsDatastore(datastoreName, hostScsiDisk);
+        morDs = firstHostDatastoreSystemMO.createVmfsDatastore(datastoreName, hostScsiDisk);
 
         if (morDs != null) {
-            rescanAllHosts(context, lstHosts);
+            waitForAllHostsToMountDatastore(lstHosts, new DatastoreMO(context, morDs));
+
+            expandDatastore(firstHostDatastoreSystemMO, new DatastoreMO(context, morDs));
 
             return morDs;
         }
@@ -1888,6 +2601,89 @@ public class VmwareStorageProcessor implements StorageProcessor {
         throw new Exception("Unable to create a datastore");
     }
 
+    private void waitForAllHostsToSeeDatastore(List<Pair<ManagedObjectReference, String>> lstHosts, DatastoreMO dsMO) throws Exception {
+        long secondsToWait = 120;
+        long endWaitTime = System.currentTimeMillis() + secondsToWait * 1000;
+
+        boolean isConditionMet = false;
+
+        while (System.currentTimeMillis() < endWaitTime && !isConditionMet) {
+            Thread.sleep(5000);
+
+            isConditionMet = verifyAllHostsSeeDatastore(lstHosts, dsMO);
+        }
+
+        if (!isConditionMet) {
+            throw new CloudRuntimeException("Not all hosts mounted the datastore");
+        }
+    }
+
+    private boolean verifyAllHostsSeeDatastore(List<Pair<ManagedObjectReference, String>> lstHosts, DatastoreMO dsMO) throws Exception {
+        int numHostsChecked = 0;
+
+        for (Pair<ManagedObjectReference, String> host: lstHosts) {
+            ManagedObjectReference morHostToMatch = host.first();
+            HostMO hostToMatchMO = new HostMO(dsMO.getContext(), morHostToMatch);
+
+            List<DatastoreHostMount> datastoreHostMounts = dsMO.getHostMounts();
+
+            for (DatastoreHostMount datastoreHostMount : datastoreHostMounts) {
+                ManagedObjectReference morHost = datastoreHostMount.getKey();
+                HostMO hostMO = new HostMO(dsMO.getContext(), morHost);
+
+                if (hostMO.getHostName().equals(hostToMatchMO.getHostName())) {
+                    numHostsChecked++;
+                }
+            }
+        }
+
+        return lstHosts.size() == numHostsChecked;
+    }
+
+    private void waitForAllHostsToMountDatastore(List<Pair<ManagedObjectReference, String>> lstHosts, DatastoreMO dsMO) throws Exception {
+        long secondsToWait = 120;
+        long endWaitTime = System.currentTimeMillis() + secondsToWait * 1000;
+
+        boolean isConditionMet = false;
+
+        while (System.currentTimeMillis() < endWaitTime && !isConditionMet) {
+            Thread.sleep(5000);
+
+            isConditionMet = verifyAllHostsMountedDatastore(lstHosts, dsMO);
+        }
+
+        if (!isConditionMet) {
+            throw new CloudRuntimeException("Not all hosts mounted the datastore");
+        }
+    }
+
+    private boolean verifyAllHostsMountedDatastore(List<Pair<ManagedObjectReference, String>> lstHosts, DatastoreMO dsMO) throws Exception {
+        int numHostsChecked = 0;
+
+        for (Pair<ManagedObjectReference, String> host: lstHosts) {
+            ManagedObjectReference morHostToMatch = host.first();
+            HostMO hostToMatchMO = new HostMO(dsMO.getContext(), morHostToMatch);
+
+            List<DatastoreHostMount> datastoreHostMounts = dsMO.getHostMounts();
+
+            for (DatastoreHostMount datastoreHostMount : datastoreHostMounts) {
+                ManagedObjectReference morHost = datastoreHostMount.getKey();
+                HostMO hostMO = new HostMO(dsMO.getContext(), morHost);
+
+                if (hostMO.getHostName().equals(hostToMatchMO.getHostName())) {
+                    if (datastoreHostMount.getMountInfo().isMounted() && datastoreHostMount.getMountInfo().isAccessible()) {
+                        numHostsChecked++;
+                    }
+                    else {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return lstHosts.size() == numHostsChecked;
+    }
+
     // the purpose of this method is to find the HostScsiDisk in the passed-in array that exists (if any) because
     // we added the static iqn to an iSCSI HBA
     private static HostScsiDisk getHostScsiDisk(HostScsiTopology hst, List<HostScsiDisk> lstHostScsiDisks, String iqn) {
@@ -1914,27 +2710,130 @@ public class VmwareStorageProcessor implements StorageProcessor {
         return null;
     }
 
-    private void removeVmfsDatastore(VmwareHypervisorHost hyperHost, String datastoreName, String storageIpAddress, int storagePortNumber, String iqn) throws Exception {
-        // hyperHost.unmountDatastore(datastoreName);
+    private boolean isDatastoreMounted(DatastoreMO dsMO, HostMO hostToMatchMO) throws Exception {
+        List<DatastoreHostMount> datastoreHostMounts = dsMO.getHostMounts();
 
-        VmwareContext context = hostService.getServiceContext(null);
+        for (DatastoreHostMount datastoreHostMount : datastoreHostMounts) {
+            ManagedObjectReference morHost = datastoreHostMount.getKey();
+            HostMO hostMO = new HostMO(dsMO.getContext(), morHost);
+
+            if (hostMO.getHostName().equals(hostToMatchMO.getHostName())) {
+                return datastoreHostMount.getMountInfo().isMounted();
+            }
+        }
+
+        throw new CloudRuntimeException("Unable to locate the applicable host");
+    }
+
+    private String getDatastoreUuid(DatastoreMO dsMO, HostMO hostToMatchMO) throws Exception {
+        List<DatastoreHostMount> datastoreHostMounts = dsMO.getHostMounts();
+
+        for (DatastoreHostMount datastoreHostMount : datastoreHostMounts) {
+            ManagedObjectReference morHost = datastoreHostMount.getKey();
+            HostMO hostMO = new HostMO(dsMO.getContext(), morHost);
+
+            if (hostMO.getHostName().equals(hostToMatchMO.getHostName())) {
+                String path = datastoreHostMount.getMountInfo().getPath();
+
+                String searchStr = "/vmfs/volumes/";
+                int index = path.indexOf(searchStr);
+
+                if (index == -1) {
+                    throw new CloudRuntimeException("Unable to find the following search string: " + searchStr);
+                }
+
+                return path.substring(index + searchStr.length());
+            }
+        }
+
+        throw new CloudRuntimeException("Unable to locate the UUID of the datastore");
+    }
+
+    private void mountVmfsDatastore(DatastoreMO dsMO, List<Pair<ManagedObjectReference, String>> hosts) throws Exception {
+        for (Pair<ManagedObjectReference, String> host : hosts) {
+            HostMO hostMO = new HostMO(dsMO.getContext(), host.first());
+
+            if (!isDatastoreMounted(dsMO, hostMO)) {
+                HostStorageSystemMO hostStorageSystemMO = hostMO.getHostStorageSystemMO();
+
+                try {
+                    hostStorageSystemMO.mountVmfsVolume(getDatastoreUuid(dsMO, hostMO));
+                }
+                catch (InvalidStateFaultMsg ex) {
+                    List<Pair<ManagedObjectReference, String>> currentHosts = new ArrayList<>(1);
+
+                    currentHosts.add(host);
+
+                    waitForAllHostsToMountDatastore(currentHosts, dsMO);
+                }
+            }
+        }
+    }
+
+    private void unmountVmfsDatastore(VmwareContext context, VmwareHypervisorHost hyperHost, String datastoreName,
+                                      List<Pair<ManagedObjectReference, String>> hosts) throws Exception {
+        ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, datastoreName);
+        DatastoreMO dsMO = new DatastoreMO(context, morDs);
+
+        for (Pair<ManagedObjectReference, String> host : hosts) {
+            HostMO hostMO = new HostMO(context, host.first());
+
+            HostStorageSystemMO hostStorageSystemMO = hostMO.getHostStorageSystemMO();
+
+            hostStorageSystemMO.unmountVmfsVolume(getDatastoreUuid(dsMO, hostMO));
+        }
+    }
+
+    private List<HostInternetScsiHbaStaticTarget> getTargets(List<Map<String, String>> targets) {
+        List<HostInternetScsiHbaStaticTarget> iScsiTargets = new ArrayList<>();
+
+        for (Map<String, String> target : targets) {
+            HostInternetScsiHbaStaticTarget iScsiTarget = new HostInternetScsiHbaStaticTarget();
+
+            iScsiTarget.setAddress(target.get(ModifyTargetsCommand.STORAGE_HOST));
+            iScsiTarget.setPort(Integer.parseInt(target.get(ModifyTargetsCommand.STORAGE_PORT)));
+            iScsiTarget.setIScsiName(trimIqn(target.get(ModifyTargetsCommand.IQN)));
+
+            iScsiTargets.add(iScsiTarget);
+        }
+
+        return iScsiTargets;
+    }
+
+    private void removeVmfsDatastore(Command cmd, VmwareHypervisorHost hyperHost, String datastoreName, String storageIpAddress, int storagePortNumber,
+                                     String iqn) throws Exception {
+        VmwareContext context = hostService.getServiceContext(cmd);
         ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
         ClusterMO cluster = new ClusterMO(context, morCluster);
         List<Pair<ManagedObjectReference, String>> lstHosts = cluster.getClusterHosts();
 
-        HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget();
+        removeVmfsDatastore(cmd, hyperHost, datastoreName, storageIpAddress, storagePortNumber, iqn, lstHosts);
+    }
 
-        target.setAddress(storageIpAddress);
-        target.setPort(storagePortNumber);
-        target.setIScsiName(iqn);
+    private void removeVmfsDatastore(Command cmd, VmwareHypervisorHost hyperHost, String datastoreName, String storageIpAddress, int storagePortNumber,
+                                     String iqn, List<Pair<ManagedObjectReference, String>> lstHosts) throws Exception {
+        VmwareContext context = hostService.getServiceContext(cmd);
 
-        final List<HostInternetScsiHbaStaticTarget> lstTargets = new ArrayList<HostInternetScsiHbaStaticTarget>();
+        unmountVmfsDatastore(context, hyperHost, datastoreName, lstHosts);
 
-        lstTargets.add(target);
+        HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(context, storageIpAddress, lstHosts);
+        List<HostMO> hostsUsingStaticDiscovery = hostDiscoveryMethod.getHostsUsingStaticDiscovery();
+
+        if (hostsUsingStaticDiscovery != null && hostsUsingStaticDiscovery.size() > 0) {
+            HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget();
+
+            target.setAddress(storageIpAddress);
+            target.setPort(storagePortNumber);
+            target.setIScsiName(iqn);
+
+            final List<HostInternetScsiHbaStaticTarget> lstTargets = new ArrayList<>();
+
+            lstTargets.add(target);
 
-        addRemoveInternetScsiTargetsToAllHosts(context, false, lstTargets, lstHosts);
+            addRemoveInternetScsiTargetsToAllHosts(false, lstTargets, hostsUsingStaticDiscovery);
 
-        rescanAllHosts(context, lstHosts);
+            rescanAllHosts(hostsUsingStaticDiscovery, true, false);
+        }
     }
 
     private void createVmdk(Command cmd, DatastoreMO dsMo, String vmdkDatastorePath, Long volumeSize) throws Exception {
@@ -1960,59 +2859,105 @@ public class VmwareStorageProcessor implements StorageProcessor {
         return (int)(bytes / (1024L * 1024L));
     }
 
-    public void handleTargetsForHost(boolean add, List<Map<String, String>> targets, HostMO host) throws Exception {
-        List<HostInternetScsiHbaStaticTarget> lstTargets = new ArrayList<HostInternetScsiHbaStaticTarget>();
+    public void handleTargets(boolean add, ModifyTargetsCommand.TargetTypeToRemove targetTypeToRemove, boolean isRemoveAsync,
+                              List<Map<String, String>> targets, List<HostMO> lstHosts) throws Exception {
+        ExecutorService executorService = Executors.newFixedThreadPool(lstHosts.size());
+
+        for (HostMO host : lstHosts) {
+            List<HostMO> hosts = new ArrayList<>();
 
-        for (Map<String, String> mapTarget : targets) {
-            HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget();
+            hosts.add(host);
 
-            String targetAddress = mapTarget.get(ModifyTargetsCommand.STORAGE_HOST);
-            Integer targetPort = Integer.parseInt(mapTarget.get(ModifyTargetsCommand.STORAGE_PORT));
-            String iScsiName = trimIqn(mapTarget.get(ModifyTargetsCommand.IQN));
+            List<Map<String, String>> dynamicTargetsForHost = new ArrayList<>();
+            List<Map<String, String>> staticTargetsForHost = new ArrayList<>();
 
-            target.setAddress(targetAddress);
-            target.setPort(targetPort);
-            target.setIScsiName(iScsiName);
+            for (Map<String, String> target : targets) {
+                String storageAddress = target.get(ModifyTargetsCommand.STORAGE_HOST);
 
-            String chapName = mapTarget.get(ModifyTargetsCommand.CHAP_NAME);
-            String chapSecret = mapTarget.get(ModifyTargetsCommand.CHAP_SECRET);
+                HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(storageAddress, hosts);
+                List<HostMO> hostsUsingDynamicDiscovery = hostDiscoveryMethod.getHostsUsingDynamicDiscovery();
 
-            if (StringUtils.isNotBlank(chapName) && StringUtils.isNotBlank(chapSecret)) {
-                HostInternetScsiHbaAuthenticationProperties auth = new HostInternetScsiHbaAuthenticationProperties();
+                if (hostsUsingDynamicDiscovery != null && hostsUsingDynamicDiscovery.size() > 0) {
+                    dynamicTargetsForHost.add(target);
+                }
+                else {
+                    staticTargetsForHost.add(target);
+                }
+            }
 
-                String strAuthType = "chapRequired";
+            if (add) {
+                executorService.submit(new Thread(() -> {
+                    try {
+                        boolean rescan = false;
 
-                auth.setChapAuthEnabled(true);
-                auth.setChapInherited(false);
-                auth.setChapAuthenticationType(strAuthType);
-                auth.setChapName(chapName);
-                auth.setChapSecret(chapSecret);
+                        if (staticTargetsForHost.size() > 0) {
+                            addRemoveInternetScsiTargetsToAllHosts(true, getTargets(staticTargetsForHost), hosts);
 
-                String mutualChapName = mapTarget.get(ModifyTargetsCommand.MUTUAL_CHAP_NAME);
-                String mutualChapSecret = mapTarget.get(ModifyTargetsCommand.MUTUAL_CHAP_SECRET);
+                            rescan = true;
+                        }
 
-                if (StringUtils.isNotBlank(mutualChapName) && StringUtils.isNotBlank(mutualChapSecret)) {
-                    auth.setMutualChapInherited(false);
-                    auth.setMutualChapAuthenticationType(strAuthType);
-                    auth.setMutualChapName(mutualChapName);
-                    auth.setMutualChapSecret(mutualChapSecret);
-                }
+                        if (dynamicTargetsForHost.size() > 0) {
+                            rescan = true;
+                        }
 
-                target.setAuthenticationProperties(auth);
+                        if (rescan) {
+                            rescanAllHosts(hosts, true, false);
+                        }
+                    }
+                    catch (Exception ex) {
+                        s_logger.warn(ex.getMessage());
+                    }
+                }));
             }
+            else {
+                List<HostInternetScsiHbaStaticTarget> targetsToRemove = new ArrayList<>();
 
-            lstTargets.add(target);
-        }
+                if (staticTargetsForHost.size() > 0 &&
+                        (ModifyTargetsCommand.TargetTypeToRemove.STATIC.equals(targetTypeToRemove) || ModifyTargetsCommand.TargetTypeToRemove.BOTH.equals(targetTypeToRemove))) {
+                    targetsToRemove.addAll(getTargets(staticTargetsForHost));
+                }
 
-        List<HostMO> hosts = new ArrayList<>();
+                if (dynamicTargetsForHost.size() > 0 &&
+                        (ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC.equals(targetTypeToRemove) || ModifyTargetsCommand.TargetTypeToRemove.BOTH.equals(targetTypeToRemove))) {
+                    targetsToRemove.addAll(getTargets(dynamicTargetsForHost));
+                }
+
+                if (targetsToRemove.size() > 0) {
+                    if (isRemoveAsync) {
+                        new Thread(() -> {
+                            try {
+                                addRemoveInternetScsiTargetsToAllHosts(false, targetsToRemove, hosts);
+
+                                rescanAllHosts(hosts, true, false);
+                            } catch (Exception ex) {
+                                s_logger.warn(ex.getMessage());
+                            }
+                        }).start();
+                    } else {
+                        executorService.submit(new Thread(() -> {
+                            try {
+                                addRemoveInternetScsiTargetsToAllHosts(false, targetsToRemove, hosts);
+
+                                rescanAllHosts(hosts, true, false);
+                            }
+                            catch (Exception ex) {
+                                s_logger.warn(ex.getMessage());
+                            }
+                        }));
+                    }
+                }
+            }
+        }
 
-        hosts.add(host);
+        executorService.shutdown();
 
-        addRemoveInternetScsiTargetsToAllHosts(add, lstTargets, hosts);
+        if (!executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES)) {
+            throw new Exception("The system timed out before completing the task 'handleTargets'.");
+        }
     }
 
     private void addRemoveInternetScsiTargetsToAllHosts(VmwareContext context, final boolean add, final List<HostInternetScsiHbaStaticTarget> targets,
-            List<Pair<ManagedObjectReference, String>> hostPairs) throws Exception {
+                                                        List<Pair<ManagedObjectReference, String>> hostPairs) throws Exception {
         List<HostMO> hosts = new ArrayList<>();
 
         for (Pair<ManagedObjectReference, String> hostPair : hostPairs) {
@@ -2024,11 +2969,11 @@ public class VmwareStorageProcessor implements StorageProcessor {
         addRemoveInternetScsiTargetsToAllHosts(add, targets, hosts);
     }
 
-    private void addRemoveInternetScsiTargetsToAllHosts(final boolean add, final List<HostInternetScsiHbaStaticTarget> targets,
-            List<HostMO> hosts) throws Exception {
+    private void addRemoveInternetScsiTargetsToAllHosts(boolean add, List<HostInternetScsiHbaStaticTarget> targets,
+                                                        List<HostMO> hosts) throws Exception {
         ExecutorService executorService = Executors.newFixedThreadPool(hosts.size());
 
-        final List<Exception> exceptions = new ArrayList<Exception>();
+        final List<Exception> exceptions = new ArrayList<>();
 
         for (HostMO host : hosts) {
             HostStorageSystemMO hostStorageSystem = host.getHostStorageSystemMO();
@@ -2036,34 +2981,26 @@ public class VmwareStorageProcessor implements StorageProcessor {
             boolean iScsiHbaConfigured = false;
 
             for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) {
-                if (hba instanceof HostInternetScsiHba) {
-                    // just finding an instance of HostInternetScsiHba means that we have found at least one configured iSCSI HBA
-                    // at least one iSCSI HBA must be configured before a CloudStack user can use this host for iSCSI storage
+                if (hba instanceof HostInternetScsiHba && ((HostInternetScsiHba)hba).isIsSoftwareBased()) {
                     iScsiHbaConfigured = true;
 
                     final String iScsiHbaDevice = hba.getDevice();
 
                     final HostStorageSystemMO hss = hostStorageSystem;
 
-                    executorService.submit(new Thread() {
-                        @Override
-                        public void run() {
-                            try {
-                                if (add) {
-                                    hss.addInternetScsiStaticTargets(iScsiHbaDevice, targets);
-                                } else {
-                                    hss.removeInternetScsiStaticTargets(iScsiHbaDevice, targets);
-                                }
-
-                                hss.rescanHba(iScsiHbaDevice);
-                                hss.rescanVmfs();
-                            } catch (Exception ex) {
-                                synchronized (exceptions) {
-                                    exceptions.add(ex);
-                                }
+                    executorService.submit(new Thread(() -> {
+                        try {
+                            if (add) {
+                                hss.addInternetScsiStaticTargets(iScsiHbaDevice, targets);
+                            } else {
+                                hss.removeInternetScsiStaticTargets(iScsiHbaDevice, targets);
+                            }
+                        } catch (Exception ex) {
+                            synchronized (exceptions) {
+                                exceptions.add(ex);
                             }
                         }
-                    });
+                    }));
                 }
             }
 
@@ -2075,7 +3012,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
         executorService.shutdown();
 
         if (!executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES)) {
-            throw new Exception("The system timed out before completing the task 'rescanAllHosts'.");
+            throw new Exception("The system timed out before completing the task 'addRemoveInternetScsiTargetsToAllHosts'.");
         }
 
         if (exceptions.size() > 0) {
@@ -2083,40 +3020,56 @@ public class VmwareStorageProcessor implements StorageProcessor {
         }
     }
 
-    private void rescanAllHosts(VmwareContext context, List<Pair<ManagedObjectReference, String>> lstHosts) throws Exception {
+    private void rescanAllHosts(VmwareContext context, List<Pair<ManagedObjectReference, String>> lstHostPairs, boolean rescanHba, boolean rescanVmfs) throws Exception {
+        List<HostMO> hosts = new ArrayList<>(lstHostPairs.size());
+
+        for (Pair<ManagedObjectReference, String> hostPair : lstHostPairs) {
+            HostMO host = new HostMO(context, hostPair.first());
+
+            hosts.add(host);
+        }
+
+        rescanAllHosts(hosts, rescanHba, rescanVmfs);
+    }
+
+    private void rescanAllHosts(List<HostMO> lstHosts, boolean rescanHba, boolean rescanVmfs) throws Exception {
+        if (!rescanHba && !rescanVmfs) {
+            // nothing to do
+            return;
+        }
+
         ExecutorService executorService = Executors.newFixedThreadPool(lstHosts.size());
 
-        final List<Exception> exceptions = new ArrayList<Exception>();
+        final List<Exception> exceptions = new ArrayList<>();
 
-        for (Pair<ManagedObjectReference, String> hostPair : lstHosts) {
-            HostMO host = new HostMO(context, hostPair.first());
+        for (HostMO host : lstHosts) {
             HostStorageSystemMO hostStorageSystem = host.getHostStorageSystemMO();
 
             boolean iScsiHbaConfigured = false;
 
             for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) {
-                if (hba instanceof HostInternetScsiHba) {
-                    // just finding an instance of HostInternetScsiHba means that we have found at least one configured iSCSI HBA
-                    // at least one iSCSI HBA must be configured before a CloudStack user can use this host for iSCSI storage
+                if (hba instanceof HostInternetScsiHba && ((HostInternetScsiHba)hba).isIsSoftwareBased()) {
                     iScsiHbaConfigured = true;
 
                     final String iScsiHbaDevice = hba.getDevice();
 
                     final HostStorageSystemMO hss = hostStorageSystem;
 
-                    executorService.submit(new Thread() {
-                        @Override
-                        public void run() {
-                            try {
+                    executorService.submit(new Thread(() -> {
+                        try {
+                            if (rescanHba) {
                                 hss.rescanHba(iScsiHbaDevice);
+                            }
+
+                            if (rescanVmfs) {
                                 hss.rescanVmfs();
-                            } catch (Exception ex) {
-                                synchronized (exceptions) {
-                                    exceptions.add(ex);
-                                }
+                            }
+                        } catch (Exception ex) {
+                            synchronized (exceptions) {
+                                exceptions.add(ex);
                             }
                         }
-                    });
+                    }));
                 }
             }
 
@@ -2140,7 +3093,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
         String[] tmp = iqn.split("/");
 
         if (tmp.length != 3) {
-            String msg = "Wrong format for iScsi path: " + iqn + ". It should be formatted as '/targetIQN/LUN'.";
+            String msg = "Wrong format for iSCSI path: " + iqn + ". It should be formatted as '/targetIQN/LUN'.";
 
             s_logger.warn(msg);
 
@@ -2151,8 +3104,8 @@ public class VmwareStorageProcessor implements StorageProcessor {
     }
 
     public ManagedObjectReference prepareManagedStorage(VmwareContext context, VmwareHypervisorHost hyperHost, String diskUuid, String iScsiName,
-            String storageHost, int storagePort, String volumeName, String chapInitiatorUsername, String chapInitiatorSecret,
-            String chapTargetUsername, String chapTargetSecret, long size, Command cmd) throws Exception {
+                                                        String storageHost, int storagePort, String volumeName, String chapInitiatorUsername, String chapInitiatorSecret,
+                                                        String chapTargetUsername, String chapTargetSecret, long size, Command cmd) throws Exception {
 
         ManagedObjectReference morDs = prepareManagedDatastore(context, hyperHost, diskUuid, iScsiName, storageHost, storagePort,
                 chapInitiatorUsername, chapInitiatorSecret, chapTargetUsername, chapTargetSecret);
@@ -2168,42 +3121,62 @@ public class VmwareStorageProcessor implements StorageProcessor {
         return morDs;
     }
 
-    public void handleDatastoreAndVmdkDetach(String datastoreName, String iqn, String storageHost, int storagePort) throws Exception {
+    public void handleDatastoreAndVmdkDetach(Command cmd, String datastoreName, String iqn, String storageHost, int storagePort) throws Exception {
         VmwareContext context = hostService.getServiceContext(null);
         VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
 
-        removeVmfsDatastore(hyperHost, datastoreName, storageHost, storagePort, trimIqn(iqn));
+        removeVmfsDatastore(cmd, hyperHost, datastoreName, storageHost, storagePort, trimIqn(iqn));
     }
 
-    private void handleDatastoreAndVmdkDetachManaged(String diskUuid, String iqn, String storageHost, int storagePort) throws Exception {
+    private void handleDatastoreAndVmdkDetachManaged(Command cmd, String diskUuid, String iqn, String storageHost, int storagePort) throws Exception {
         if (storagePort == DEFAULT_NFS_PORT) {
             VmwareContext context = hostService.getServiceContext(null);
             VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
             // for managed NFS datastore
             hyperHost.unmountDatastore(diskUuid);
         } else {
-            handleDatastoreAndVmdkDetach(VmwareResource.getDatastoreName(iqn), iqn, storageHost, storagePort);
+            handleDatastoreAndVmdkDetach(cmd, VmwareResource.getDatastoreName(iqn), iqn, storageHost, storagePort);
+        }
+    }
+
+    private class ManagedTarget {
+        private final String storageAddress;
+        private final int storagePort;
+        private final String iqn;
+
+        ManagedTarget(String storageAddress, int storagePort, String iqn) {
+            this.storageAddress = storageAddress;
+            this.storagePort = storagePort;
+            this.iqn = iqn;
+        }
+
+        public String toString() {
+            return storageAddress + storagePort + iqn;
         }
     }
 
-    private void removeManagedTargetsFromCluster(List<String> iqns) throws Exception {
-        List<HostInternetScsiHbaStaticTarget> lstManagedTargets = new ArrayList<HostInternetScsiHbaStaticTarget>();
+    private void removeManagedTargetsFromCluster(List<String> managedDatastoreNames) throws Exception {
+        List<HostInternetScsiHbaStaticTarget> lstManagedTargets = new ArrayList<>();
 
         VmwareContext context = hostService.getServiceContext(null);
         VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
         ManagedObjectReference morCluster = hyperHost.getHyperHostCluster();
         ClusterMO cluster = new ClusterMO(context, morCluster);
         List<Pair<ManagedObjectReference, String>> lstHosts = cluster.getClusterHosts();
-        HostMO host = new HostMO(context, lstHosts.get(0).first());
-        HostStorageSystemMO hostStorageSystem = host.getHostStorageSystemMO();
+        HostMO hostMO = new HostMO(context, lstHosts.get(0).first());
+        HostStorageSystemMO hostStorageSystem = hostMO.getHostStorageSystemMO();
+
+        for (String managedDatastoreName : managedDatastoreNames) {
+            unmountVmfsDatastore(context, hyperHost, managedDatastoreName, lstHosts);
+        }
 
         for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) {
-            if (hba instanceof HostInternetScsiHba) {
+            if (hba instanceof HostInternetScsiHba && ((HostInternetScsiHba)hba).isIsSoftwareBased()) {
                 List<HostInternetScsiHbaStaticTarget> lstTargets = ((HostInternetScsiHba)hba).getConfiguredStaticTarget();
 
                 if (lstTargets != null) {
                     for (HostInternetScsiHbaStaticTarget target : lstTargets) {
-                        if (iqns.contains(target.getIScsiName())) {
+                        if (managedDatastoreNames.contains(VmwareResource.createDatastoreNameFromIqn(target.getIScsiName()))) {
                             lstManagedTargets.add(target);
                         }
                     }
@@ -2211,13 +3184,49 @@ public class VmwareStorageProcessor implements StorageProcessor {
             }
         }
 
-        addRemoveInternetScsiTargetsToAllHosts(context, false, lstManagedTargets, lstHosts);
+        ExecutorService executorService = Executors.newFixedThreadPool(lstHosts.size());
+
+        for (Pair<ManagedObjectReference, String> host : lstHosts) {
+            List<Pair<ManagedObjectReference, String>> hosts = new ArrayList<>();
+
+            hosts.add(host);
+
+            List<HostInternetScsiHbaStaticTarget> staticTargetsForHost = new ArrayList<>();
+
+            for (HostInternetScsiHbaStaticTarget iScsiManagedTarget : lstManagedTargets) {
+                String storageAddress = iScsiManagedTarget.getAddress();
+
+                HostDiscoveryMethod hostDiscoveryMethod = getHostDiscoveryMethod(context, storageAddress, hosts);
+                List<HostMO> hostsUsingStaticDiscovery = hostDiscoveryMethod.getHostsUsingStaticDiscovery();
+
+                if (hostsUsingStaticDiscovery != null && hostsUsingStaticDiscovery.size() > 0) {
+                    staticTargetsForHost.add(iScsiManagedTarget);
+                }
+            }
+
+            if (staticTargetsForHost.size() > 0) {
+                executorService.submit(new Thread(() -> {
+                    try {
+                        addRemoveInternetScsiTargetsToAllHosts(context, false, staticTargetsForHost, hosts);
+
+                        rescanAllHosts(context, hosts, true, false);
+                    }
+                    catch (Exception ex) {
+                        s_logger.warn(ex.getMessage());
+                    }
+                }));
+            }
+        }
+
+        executorService.shutdown();
 
-        rescanAllHosts(context, lstHosts);
+        if (!executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES)) {
+            throw new Exception("The system timed out before completing the task 'removeManagedTargetsFromCluster'.");
+        }
     }
 
-    private List<String> getManagedIqnsFromVirtualDisks(List<VirtualDisk> virtualDisks) {
-        List<String> managedIqns = new ArrayList<String>();
+    private List<String> getManagedDatastoreNamesFromVirtualDisks(List<VirtualDisk> virtualDisks) {
+        List<String> managedDatastoreNames = new ArrayList<>();
 
         if (virtualDisks != null) {
             for (VirtualDisk virtualDisk : virtualDisks) {
@@ -2239,7 +3248,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
                             path = path.substring(0, index);
 
                             if (path.startsWith("iqn.")) {
-                                managedIqns.add(path);
+                                managedDatastoreNames.add("-" + path + "-0");
                             }
                         }
                     }
@@ -2247,15 +3256,15 @@ public class VmwareStorageProcessor implements StorageProcessor {
             }
         }
 
-        return managedIqns;
+        return managedDatastoreNames;
     }
 
     private Long restoreVolumeFromSecStorage(VmwareHypervisorHost hyperHost, DatastoreMO primaryDsMo, String newVolumeName, String secStorageUrl, String secStorageDir,
-            String backupName, long wait, Integer nfsVersion) throws Exception {
+                                             String backupName, long wait, Integer nfsVersion) throws Exception {
 
         String secondaryMountPoint = mountService.getMountPoint(secStorageUrl, null);
-        String srcOVAFileName = null;
-        String srcOVFFileName = null;
+        String srcOVAFileName;
+        String srcOVFFileName;
 
         srcOVAFileName = secondaryMountPoint + "/" + secStorageDir + "/" + backupName + "." + ImageFormat.OVA.getFileExtension();
         srcOVFFileName = secondaryMountPoint + "/" + secStorageDir + "/" + backupName + ".ovf";
@@ -2335,7 +3344,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
         int index = backedUpSnapshotUuid.lastIndexOf(File.separator);
         String backupPath = backedUpSnapshotUuid.substring(0, index);
         backedUpSnapshotUuid = backedUpSnapshotUuid.substring(index + 1);
-        String details = null;
+        String details;
         String newVolumeName = VmwareHelper.getVCenterSafeUuid();
 
         VmwareContext context = hostService.getServiceContext(cmd);
@@ -2405,36 +3414,16 @@ public class VmwareStorageProcessor implements StorageProcessor {
         return templateUuid;
     }
 
-    private String getControllerFromConfigurationSetting() throws Exception {
-        String diskController = null;
-        VmwareContext context = null;
-        try {
-            context = hostService.getServiceContext(null);
-            VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME);
-            diskController = mgr.getDataDiskController();
-        } catch (Throwable e) {
-            if (e instanceof RemoteException) {
-                s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context");
-                hostService.invalidateServiceContext(context);
-            }
-
-            String details = "Failed to connect to vCenter due to " + VmwareHelper.getExceptionMessage(e);
-            s_logger.error(details, e);
-        }
-
-        return diskController;
-    }
-
     private String getLegacyVmDataDiskController() throws Exception {
         return DiskControllerType.lsilogic.toString();
     }
 
-    public void setNfsVersion(Integer nfsVersion){
+    void setNfsVersion(Integer nfsVersion){
         this._nfsVersion = nfsVersion;
         s_logger.debug("VmwareProcessor instance now using NFS version: " + nfsVersion);
     }
 
-    public void setFullCloneFlag(boolean value){
+    void setFullCloneFlag(boolean value){
         this._fullCloneFlag = value;
         s_logger.debug("VmwareProcessor instance - create full clone = " + (value ? "TRUE" : "FALSE"));
     }
diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java
index 97d6118..4b7080a 100644
--- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java
+++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java
@@ -2459,12 +2459,12 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
         return sr;
     }
 
-    private String resignatureIscsiSr(Connection conn, Host host, Map<String, String> deviceConfig, String srNameLabel, Map<String, String> smConfig) throws XmlRpcException, XenAPIException {
-
+    private String resignatureIscsiSr(Connection conn, Host host, Map<String, String> deviceConfig, String srNameLabel, Map<String, String> smConfig)
+            throws XmlRpcException, XenAPIException {
         String pooluuid;
+
         try {
-            SR.create(conn, host, deviceConfig, new Long(0), srNameLabel, srNameLabel, SRType.RELVMOISCSI.toString(),
-                        "user", true, smConfig);
+            SR.create(conn, host, deviceConfig, new Long(0), srNameLabel, srNameLabel, SRType.RELVMOISCSI.toString(), "user", true, smConfig);
 
             // The successful outcome of SR.create (right above) is to throw an exception of type XenAPIException (with expected
             // toString() text) after resigning the metadata (we indicated to perform a resign by passing in SRType.RELVMOISCSI.toString()).
@@ -2492,6 +2492,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
                 throw new CloudRuntimeException("Non-existent or invalid SR UUID");
             }
         }
+
         return pooluuid;
     }
 
diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java
index 0ff2a47..257c6a2 100644
--- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java
+++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java
@@ -298,7 +298,7 @@ public class XenServerStorageProcessor implements StorageProcessor {
                 return new AttachAnswer(disk);
             }
 
-            VDI vdi = null;
+            VDI vdi;
 
             if (isManaged) {
                 vdi = hypervisorResource.prepareManagedStorage(conn, details, data.getPath(), vdiNameLabel);
@@ -320,10 +320,13 @@ public class XenServerStorageProcessor implements StorageProcessor {
             vbdr.VDI = vdi;
             vbdr.bootable = false;
             vbdr.userdevice = "autodetect";
+
             final Long deviceId = disk.getDiskSeq();
+
             if (deviceId != null && !hypervisorResource.isDeviceUsed(conn, vm, deviceId)) {
                 vbdr.userdevice = deviceId.toString();
             }
+
             vbdr.mode = Types.VbdMode.RW;
             vbdr.type = Types.VbdType.DISK;
             vbdr.unpluggable = true;
@@ -337,6 +340,7 @@ public class XenServerStorageProcessor implements StorageProcessor {
                 vbd.destroy(conn);
                 throw e;
             }
+
... 6091 lines suppressed ...

-- 
To stop receiving notification emails like this one, please contact
['"commits@cloudstack.apache.org" <commits@cloudstack.apache.org>'].

Mime
View raw message