You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ra...@apache.org on 2016/09/13 12:11:04 UTC

[3/4] git commit: updated refs/heads/master to f21477a

Adding support for cross-cluster storage migration for managed storage when using XenServer


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/b508fb86
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/b508fb86
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/b508fb86

Branch: refs/heads/master
Commit: b508fb8692eac1675a4597c9dfaef463304aecba
Parents: 1d9735c
Author: Mike Tutkowski <mi...@solidfire.com>
Authored: Sat Aug 20 17:58:30 2016 -0600
Committer: Mike Tutkowski <mi...@solidfire.com>
Committed: Mon Sep 12 07:39:13 2016 -0600

----------------------------------------------------------------------
 api/src/com/cloud/storage/StoragePool.java      |   2 +
 .../api/MigrateWithStorageReceiveCommand.java   |  11 +-
 .../agent/test/BackupSnapshotCommandTest.java   |   3 +
 .../api/agent/test/CheckNetworkAnswerTest.java  |   3 +
 .../api/agent/test/SnapshotCommandTest.java     |   3 +
 .../api/storage/PrimaryDataStoreDriver.java     |   7 +
 .../com/cloud/vm/VirtualMachineManagerImpl.java |  88 ++-
 .../storage/datastore/db/StoragePoolVO.java     |   1 +
 ...MigrateWithStorageReceiveCommandWrapper.java |  13 +-
 .../CitrixCreateStoragePoolCommandWrapper.java  |  31 +-
 .../CitrixDeleteStoragePoolCommandWrapper.java  |  30 +-
 .../motion/XenServerStorageMotionStrategy.java  | 222 +++++-
 .../xenbase/XenServer610WrapperTest.java        |   6 +-
 .../driver/SolidFirePrimaryDataStoreDriver.java | 190 ++++-
 .../com/cloud/server/ManagementServerImpl.java  |  23 +-
 .../plugins/solidfire/TestAddRemoveHosts.py     |  58 +-
 .../plugins/solidfire/TestSnapshots.py          | 580 +++++++++++----
 .../solidfire/TestVMMigrationWithStorage.py     | 697 +++++++++++++++++++
 .../plugins/solidfire/TestVMSnapshots.py        |  74 +-
 .../plugins/solidfire/TestVolumes.py            | 548 +++++----------
 .../plugins/solidfire/util/sf_util.py           | 217 ++++++
 21 files changed, 2083 insertions(+), 724 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/api/src/com/cloud/storage/StoragePool.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/storage/StoragePool.java b/api/src/com/cloud/storage/StoragePool.java
index 8e03c33..3a2d3bd 100644
--- a/api/src/com/cloud/storage/StoragePool.java
+++ b/api/src/com/cloud/storage/StoragePool.java
@@ -104,4 +104,6 @@ public interface StoragePool extends Identity, InternalIdentity {
     boolean isInMaintenance();
 
     Hypervisor.HypervisorType getHypervisor();
+
+    boolean isManaged();
 }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java
----------------------------------------------------------------------
diff --git a/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java b/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java
index 66aecdb..3d413fc 100644
--- a/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java
+++ b/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java
@@ -21,26 +21,25 @@ package com.cloud.agent.api;
 
 import java.util.List;
 
-import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.agent.api.to.VolumeTO;
 import com.cloud.utils.Pair;
 
 public class MigrateWithStorageReceiveCommand extends Command {
     VirtualMachineTO vm;
-    List<Pair<VolumeTO, StorageFilerTO>> volumeToFiler;
+    List<Pair<VolumeTO, String>> volumeToStorageUuid;
 
-    public MigrateWithStorageReceiveCommand(VirtualMachineTO vm, List<Pair<VolumeTO, StorageFilerTO>> volumeToFiler) {
+    public MigrateWithStorageReceiveCommand(VirtualMachineTO vm, List<Pair<VolumeTO, String>> volumeToStorageUuid) {
         this.vm = vm;
-        this.volumeToFiler = volumeToFiler;
+        this.volumeToStorageUuid = volumeToStorageUuid;
     }
 
     public VirtualMachineTO getVirtualMachine() {
         return vm;
     }
 
-    public List<Pair<VolumeTO, StorageFilerTO>> getVolumeToFiler() {
-        return volumeToFiler;
+    public List<Pair<VolumeTO, String>> getVolumeToStorageUuid() {
+        return volumeToStorageUuid;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java
----------------------------------------------------------------------
diff --git a/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java
index bdcda38..edc90aa 100644
--- a/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java
+++ b/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java
@@ -135,6 +135,9 @@ public class BackupSnapshotCommandTest {
         };
 
         @Override
+        public boolean isManaged() { return false; }
+
+        @Override
         public Long getPodId() {
             return 0L;
         }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java
----------------------------------------------------------------------
diff --git a/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java b/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java
index d6f0bfc..4d49c99 100644
--- a/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java
+++ b/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java
@@ -174,6 +174,9 @@ public class CheckNetworkAnswerTest {
             };
 
             @Override
+            public boolean isManaged() { return false; }
+
+            @Override
             public Long getPodId() {
                 return 0L;
             }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java
----------------------------------------------------------------------
diff --git a/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java
index 629669a..576419a 100644
--- a/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java
+++ b/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java
@@ -136,6 +136,9 @@ public class SnapshotCommandTest {
         };
 
         @Override
+        public boolean isManaged() { return false; }
+
+        @Override
         public Long getPodId() {
             return 0L;
         }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
----------------------------------------------------------------------
diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
index 6dcdf4f..8749589 100644
--- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
+++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
@@ -25,6 +25,13 @@ import com.cloud.host.Host;
 import com.cloud.storage.StoragePool;
 
 public interface PrimaryDataStoreDriver extends DataStoreDriver {
+    String BASIC_CREATE = "basicCreate";
+    String BASIC_DELETE = "basicDelete";
+    String BASIC_DELETE_FAILURE = "basicDeleteFailure";
+    String BASIC_GRANT_ACCESS = "basicGrantAccess";
+    String BASIC_REVOKE_ACCESS = "basicRevokeAccess";
+    String BASIC_IQN = "basicIqn";
+
     ChapInfo getChapInfo(DataObject dataObject);
 
     boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
----------------------------------------------------------------------
diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
index 9523b92..a4c9889 100644
--- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -2045,62 +2045,74 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
 
     private Map<Volume, StoragePool> getPoolListForVolumesForMigration(final VirtualMachineProfile profile, final Host host, final Map<Long, Long> volumeToPool) {
         final List<VolumeVO> allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId());
-        final Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<Volume, StoragePool> ();
+        final Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<>();
+
         for (final VolumeVO volume : allVolumes) {
             final Long poolId = volumeToPool.get(Long.valueOf(volume.getId()));
-            final StoragePoolVO pool = _storagePoolDao.findById(poolId);
+            final StoragePoolVO destPool = _storagePoolDao.findById(poolId);
             final StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId());
             final DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
-            if (pool != null) {
+
+            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(pool.getId(), host.getId()) == null || pool.isLocal() != diskOffering.getUseLocalStorage()) {
+                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 " + pool + " while migrating vm to host " + host +
+                    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 (pool.getId() == currentPool.getId()) {
-                    // If the pool to migrate too is the same as current pool, the volume doesn't need to be migrated.
+                } 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, pool);
+                    volumeToPoolObjectMap.put(volume, destPool);
                 }
             } 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 ExcludeList avoid = new ExcludeList();
-                boolean currentPoolAvailable = false;
-
-                final List<StoragePool> poolList = new ArrayList<StoragePool>();
-                for (final StoragePoolAllocator allocator : _storagePoolAllocators) {
-                    final List<StoragePool> poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL);
-                    if (poolListFromAllocator != null && !poolListFromAllocator.isEmpty()) {
-                        poolList.addAll(poolListFromAllocator);
-                    }
-                }
+                if (currentPool.isManaged()) {
+                    volumeToPoolObjectMap.put(volume, currentPool);
+                } else {
+                    // Find a suitable pool for the volume. Call the storage pool allocator to find the list of pools.
 
-                if (poolList != null && !poolList.isEmpty()) {
-                    // Volume needs to be migrated. Pick the first pool from the list. Add a mapping to migrate the
-                    // volume to a pool only if it is required; that is the current pool on which the volume resides
-                    // is not available on the destination host.
-                    final Iterator<StoragePool> iter = poolList.iterator();
-                    while (iter.hasNext()) {
-                        if (currentPool.getId() == iter.next().getId()) {
-                            currentPoolAvailable = true;
-                            break;
+                    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 List<StoragePool> poolList = new ArrayList<>();
+                    final ExcludeList avoid = new ExcludeList();
+
+                    for (final StoragePoolAllocator allocator : _storagePoolAllocators) {
+                        final List<StoragePool> poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL);
+
+                        if (poolListFromAllocator != null && !poolListFromAllocator.isEmpty()) {
+                            poolList.addAll(poolListFromAllocator);
                         }
                     }
 
-                    if (!currentPoolAvailable) {
-                        volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid()));
-                    }
-                }
+                    boolean currentPoolAvailable = false;
 
+                    if (poolList != null && !poolList.isEmpty()) {
+                        // Volume needs to be migrated. Pick the first pool from the list. Add a mapping to migrate the
+                        // volume to a pool only if it is required; that is the current pool on which the volume resides
+                        // is not available on the destination host.
 
-                if (!currentPoolAvailable && !volumeToPoolObjectMap.containsKey(volume)) {
-                    // Cannot find a pool for the volume. Throw an exception.
-                    throw new CloudRuntimeException("Cannot find a storage pool which is available for volume " + volume + " while migrating virtual machine " +
-                            profile.getVirtualMachine() + " to host " + host);
+                        final Iterator<StoragePool> iter = poolList.iterator();
+
+                        while (iter.hasNext()) {
+                            if (currentPool.getId() == iter.next().getId()) {
+                                currentPoolAvailable = true;
+
+                                break;
+                            }
+                        }
+
+                        if (!currentPoolAvailable) {
+                            volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid()));
+                        }
+                    }
+
+                    if (!currentPoolAvailable && !volumeToPoolObjectMap.containsKey(volume)) {
+                        // Cannot find a pool for the volume. Throw an exception.
+                        throw new CloudRuntimeException("Cannot find a storage pool which is available for volume " + volume + " while migrating virtual machine " +
+                                profile.getVirtualMachine() + " to host " + host);
+                    }
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
index ad2ad41..24fcaa0 100644
--- a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
+++ b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
@@ -231,6 +231,7 @@ public class StoragePoolVO implements StoragePool {
         this.managed = managed;
     }
 
+    @Override
     public boolean isManaged() {
         return managed;
     }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java
index 046a425..fdcb7b5 100644
--- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java
+++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java
@@ -31,7 +31,6 @@ import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.MigrateWithStorageReceiveAnswer;
 import com.cloud.agent.api.MigrateWithStorageReceiveCommand;
 import com.cloud.agent.api.to.NicTO;
-import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.agent.api.to.VolumeTO;
 import com.cloud.hypervisor.xenserver.resource.XenServer610Resource;
@@ -56,7 +55,7 @@ public final class XenServer610MigrateWithStorageReceiveCommandWrapper extends C
     public Answer execute(final MigrateWithStorageReceiveCommand command, final XenServer610Resource xenServer610Resource) {
         final Connection connection = xenServer610Resource.getConnection();
         final VirtualMachineTO vmSpec = command.getVirtualMachine();
-        final List<Pair<VolumeTO, StorageFilerTO>> volumeToFiler = command.getVolumeToFiler();
+        final List<Pair<VolumeTO, String>> volumeToStorageUuid = command.getVolumeToStorageUuid();
 
         try {
             // In a cluster management server setup, the migrate with storage receive and send
@@ -69,10 +68,12 @@ public final class XenServer610MigrateWithStorageReceiveCommandWrapper extends C
             // storage send command execution.
             Gson gson = new Gson();
             // Get a map of all the SRs to which the vdis will be migrated.
-            final List<Pair<VolumeTO, Object>> volumeToSr = new ArrayList<Pair<VolumeTO, Object>>();
-            for (final Pair<VolumeTO, StorageFilerTO> entry : volumeToFiler) {
-                final StorageFilerTO storageFiler = entry.second();
-                final SR sr = xenServer610Resource.getStorageRepository(connection, storageFiler.getUuid());
+            final List<Pair<VolumeTO, Object>> volumeToSr = new ArrayList<>();
+
+            for (final Pair<VolumeTO, String> entry : volumeToStorageUuid) {
+                final String storageUuid = entry.second();
+                final SR sr = xenServer610Resource.getStorageRepository(connection, storageUuid);
+
                 volumeToSr.add(new Pair<VolumeTO, Object>(entry.first(), sr));
             }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java
index bed417f..7b2a599 100644
--- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java
+++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java
@@ -19,6 +19,8 @@
 
 package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase;
 
+import java.util.Map;
+
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
@@ -39,20 +41,35 @@ public final class CitrixCreateStoragePoolCommandWrapper extends CommandWrapper<
     public Answer execute(final CreateStoragePoolCommand command, final CitrixResourceBase citrixResourceBase) {
         final Connection conn = citrixResourceBase.getConnection();
         final StorageFilerTO pool = command.getPool();
+
         try {
-            if (pool.getType() == StoragePoolType.NetworkFilesystem) {
-                citrixResourceBase.getNfsSR(conn, Long.toString(pool.getId()), pool.getUuid(), pool.getHost(), pool.getPath(), pool.toString());
-            } else if (pool.getType() == StoragePoolType.IscsiLUN) {
-                citrixResourceBase.getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null, false);
-            } else if (pool.getType() == StoragePoolType.PreSetup) {
-            } else {
-                return new Answer(command, false, "The pool type: " + pool.getType().name() + " is not supported.");
+            if (command.getCreateDatastore()) {
+                Map<String, String> details = command.getDetails();
+
+                String srNameLabel = details.get(CreateStoragePoolCommand.DATASTORE_NAME);
+                String storageHost = details.get(CreateStoragePoolCommand.STORAGE_HOST);
+                String iqn = details.get(CreateStoragePoolCommand.IQN);
+
+                citrixResourceBase.getIscsiSR(conn, srNameLabel, storageHost, iqn, null, null, false);
             }
+            else {
+                if (pool.getType() == StoragePoolType.NetworkFilesystem) {
+                    citrixResourceBase.getNfsSR(conn, Long.toString(pool.getId()), pool.getUuid(), pool.getHost(), pool.getPath(), pool.toString());
+                } else if (pool.getType() == StoragePoolType.IscsiLUN) {
+                    citrixResourceBase.getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null, false);
+                } else if (pool.getType() == StoragePoolType.PreSetup) {
+                } else {
+                    return new Answer(command, false, "The pool type: " + pool.getType().name() + " is not supported.");
+                }
+            }
+
             return new Answer(command, true, "success");
         } catch (final Exception e) {
             final String msg = "Catch Exception " + e.getClass().getName() + ", create StoragePool failed due to " + e.toString() + " on host:"
                     + citrixResourceBase.getHost().getUuid() + " pool: " + pool.getHost() + pool.getPath();
+
             s_logger.warn(msg, e);
+
             return new Answer(command, false, msg);
         }
     }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java
index a9ae680..c93dd90 100644
--- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java
+++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java
@@ -19,6 +19,8 @@
 
 package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase;
 
+import java.util.Map;
+
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
@@ -32,22 +34,40 @@ import com.xensource.xenapi.SR;
 
 @ResourceWrapper(handles =  DeleteStoragePoolCommand.class)
 public final class CitrixDeleteStoragePoolCommandWrapper extends CommandWrapper<DeleteStoragePoolCommand, Answer, CitrixResourceBase> {
-
     private static final Logger s_logger = Logger.getLogger(CitrixDeleteStoragePoolCommandWrapper.class);
 
     @Override
     public Answer execute(final DeleteStoragePoolCommand command, final CitrixResourceBase citrixResourceBase) {
         final Connection conn = citrixResourceBase.getConnection();
         final StorageFilerTO poolTO = command.getPool();
+
         try {
-            final SR sr = citrixResourceBase.getStorageRepository(conn, poolTO.getUuid());
+            final SR sr;
+
+            // getRemoveDatastore being true indicates we are using managed storage and need to pull the SR name out of a Map
+            // instead of pulling it out using getUuid of the StorageFilerTO instance.
+            if (command.getRemoveDatastore()) {
+                Map<String, String> details = command.getDetails();
+
+                String srNameLabel = details.get(DeleteStoragePoolCommand.DATASTORE_NAME);
+
+                sr = citrixResourceBase.getStorageRepository(conn, srNameLabel);
+            }
+            else {
+                sr = citrixResourceBase.getStorageRepository(conn, poolTO.getUuid());
+            }
+
             citrixResourceBase.removeSR(conn, sr);
+
             final Answer answer = new Answer(command, true, "success");
+
             return answer;
         } catch (final Exception e) {
-            final String msg = "DeleteStoragePoolCommand XenAPIException:" + e.getMessage() + " host:" + citrixResourceBase.getHost().getUuid() + " pool: " + poolTO.getHost()
-                    + poolTO.getPath();
-            s_logger.warn(msg, e);
+            final String msg = "DeleteStoragePoolCommand XenAPIException:" + e.getMessage() + " host:" + citrixResourceBase.getHost().getUuid() +
+                    " pool: " + poolTO.getHost() + poolTO.getPath();
+
+            s_logger.error(msg, e);
+
             return new Answer(command, false, msg);
         }
     }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java b/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java
index 7de96b0..2409b6e 100644
--- a/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java
+++ b/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java
@@ -19,6 +19,7 @@
 package org.apache.cloudstack.storage.motion;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -28,6 +29,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
 import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
@@ -39,6 +41,8 @@ import org.springframework.stereotype.Component;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CreateStoragePoolCommand;
+import com.cloud.agent.api.DeleteStoragePoolCommand;
 import com.cloud.agent.api.MigrateWithStorageAnswer;
 import com.cloud.agent.api.MigrateWithStorageCommand;
 import com.cloud.agent.api.MigrateWithStorageCompleteAnswer;
@@ -56,9 +60,12 @@ import com.cloud.host.Host;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.VolumeVO;
+import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.Pair;
+import com.cloud.utils.StringUtils;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.dao.VMInstanceDao;
 
@@ -74,6 +81,8 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
     @Inject
     PrimaryDataStoreDao storagePoolDao;
     @Inject
+    private VolumeDetailsDao volumeDetailsDao;
+    @Inject
     VMInstanceDao instanceDao;
 
     @Override
@@ -120,25 +129,175 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
         callback.complete(result);
     }
 
+    private String getBasicIqn(long volumeId) {
+        VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, PrimaryDataStoreDriver.BASIC_IQN);
+
+        return volumeDetail.getValue();
+    }
+
+    /**
+     * Tell the underlying storage plug-in to create a new volume, put it in the VAG of the destination cluster, and
+     * send a command to the destination cluster to create an SR and to attach to the SR from all hosts in the cluster.
+     */
+    private String handleManagedVolumePreMigration(VolumeInfo volumeInfo, StoragePool storagePool, Host destHost) {
+        final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver();
+
+        VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_CREATE, Boolean.TRUE.toString(), false);
+
+        volumeDetailsDao.persist(volumeDetailVo);
+
+        pdsd.createAsync(volumeInfo.getDataStore(), volumeInfo, null);
+
+        volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_GRANT_ACCESS, Boolean.TRUE.toString(), false);
+
+        volumeDetailsDao.persist(volumeDetailVo);
+
+        pdsd.grantAccess(volumeInfo, destHost, volumeInfo.getDataStore());
+
+        final Map<String, String> details = new HashMap<>();
+
+        final String iqn = getBasicIqn(volumeInfo.getId());
+
+        details.put(CreateStoragePoolCommand.DATASTORE_NAME, iqn);
+
+        details.put(CreateStoragePoolCommand.IQN, iqn);
+
+        details.put(CreateStoragePoolCommand.STORAGE_HOST, storagePool.getHostAddress());
+
+        details.put(CreateStoragePoolCommand.STORAGE_PORT, String.valueOf(storagePool.getPort()));
+
+        final CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, storagePool);
+
+        cmd.setDetails(details);
+        cmd.setCreateDatastore(true);
+
+        final Answer answer = agentMgr.easySend(destHost.getId(), cmd);
+
+        if (answer == null || !answer.getResult()) {
+            String errMsg = "Error interacting with host (related to CreateStoragePoolCommand)" +
+                    (StringUtils.isNotBlank(answer.getDetails()) ? ": " + answer.getDetails() : "");
+
+            s_logger.error(errMsg);
+
+            throw new CloudRuntimeException(errMsg);
+        }
+
+        return iqn;
+    }
+
+    private void handleManagedVolumePostMigration(VolumeInfo volumeInfo, Host srcHost, VolumeObjectTO volumeTO) {
+        final Map<String, String> details = new HashMap<>();
+
+        details.put(DeleteStoragePoolCommand.DATASTORE_NAME, volumeInfo.get_iScsiName());
+
+        final DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand();
+
+        cmd.setDetails(details);
+        cmd.setRemoveDatastore(true);
+
+        final Answer answer = agentMgr.easySend(srcHost.getId(), cmd);
+
+        if (answer == null || !answer.getResult()) {
+            String errMsg = "Error interacting with host (related to DeleteStoragePoolCommand)" +
+                    (StringUtils.isNotBlank(answer.getDetails()) ? ": " + answer.getDetails() : "");
+
+            s_logger.error(errMsg);
+
+            throw new CloudRuntimeException(errMsg);
+        }
+
+        final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver();
+
+        pdsd.revokeAccess(volumeInfo, srcHost, volumeInfo.getDataStore());
+
+        VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_DELETE, Boolean.TRUE.toString(), false);
+
+        volumeDetailsDao.persist(volumeDetailVo);
+
+        pdsd.deleteAsync(volumeInfo.getDataStore(), volumeInfo, null);
+
+        VolumeVO volumeVO = volDao.findById(volumeInfo.getId());
+
+        volumeVO.setPath(volumeTO.getPath());
+
+        volDao.update(volumeVO.getId(), volumeVO);
+    }
+
+    private void handleManagedVolumesAfterFailedMigration(Map<VolumeInfo, DataStore> volumeToPool, Host destHost) {
+        for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
+            VolumeInfo volumeInfo = entry.getKey();
+            StoragePool storagePool = storagePoolDao.findById(volumeInfo.getPoolId());
+
+            if (storagePool.isManaged()) {
+                final Map<String, String> details = new HashMap<>();
+
+                details.put(DeleteStoragePoolCommand.DATASTORE_NAME, getBasicIqn(volumeInfo.getId()));
+
+                final DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand();
+
+                cmd.setDetails(details);
+                cmd.setRemoveDatastore(true);
+
+                final Answer answer = agentMgr.easySend(destHost.getId(), cmd);
+
+                if (answer == null || !answer.getResult()) {
+                    String errMsg = "Error interacting with host (related to handleManagedVolumesAfterFailedMigration)" +
+                            (StringUtils.isNotBlank(answer.getDetails()) ? ": " + answer.getDetails() : "");
+
+                    s_logger.error(errMsg);
+
+                    // no need to throw an exception here as the calling code is responsible for doing so
+                    // regardless of the success or lack thereof concerning this method
+                    return;
+                }
+
+                final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver();
+
+                VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_REVOKE_ACCESS, Boolean.TRUE.toString(), false);
+
+                volumeDetailsDao.persist(volumeDetailVo);
+
+                pdsd.revokeAccess(volumeInfo, destHost, volumeInfo.getDataStore());
+
+                volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_DELETE_FAILURE, Boolean.TRUE.toString(), false);
+
+                volumeDetailsDao.persist(volumeDetailVo);
+
+                pdsd.deleteAsync(volumeInfo.getDataStore(), volumeInfo, null);
+            }
+        }
+    }
+
     private Answer migrateVmWithVolumesAcrossCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, Host destHost, Map<VolumeInfo, DataStore> volumeToPool)
             throws AgentUnavailableException {
+        // Initiate migration of a virtual machine with its volumes.
 
-        // Initiate migration of a virtual machine with it's volumes.
         try {
-            List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerto = new ArrayList<Pair<VolumeTO, StorageFilerTO>>();
+            List<Pair<VolumeTO, String>> volumeToStorageUuid = new ArrayList<>();
+
             for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
-                VolumeInfo volume = entry.getKey();
-                VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId()));
-                StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue());
-                volumeToFilerto.add(new Pair<VolumeTO, StorageFilerTO>(volumeTo, filerTo));
+                VolumeInfo volumeInfo = entry.getKey();
+                StoragePool storagePool = storagePoolDao.findById(volumeInfo.getPoolId());
+                VolumeTO volumeTo = new VolumeTO(volumeInfo, storagePool);
+
+                if (storagePool.isManaged()) {
+                    String iqn = handleManagedVolumePreMigration(volumeInfo, storagePool, destHost);
+
+                    volumeToStorageUuid.add(new Pair<>(volumeTo, iqn));
+                }
+                else {
+                    volumeToStorageUuid.add(new Pair<>(volumeTo, ((StoragePool)entry.getValue()).getPath()));
+                }
             }
 
             // Migration across cluster needs to be done in three phases.
             // 1. Send a migrate receive command to the destination host so that it is ready to receive a vm.
             // 2. Send a migrate send command to the source host. This actually migrates the vm to the destination.
             // 3. Complete the process. Update the volume details.
-            MigrateWithStorageReceiveCommand receiveCmd = new MigrateWithStorageReceiveCommand(to, volumeToFilerto);
+
+            MigrateWithStorageReceiveCommand receiveCmd = new MigrateWithStorageReceiveCommand(to, volumeToStorageUuid);
             MigrateWithStorageReceiveAnswer receiveAnswer = (MigrateWithStorageReceiveAnswer)agentMgr.send(destHost.getId(), receiveCmd);
+
             if (receiveAnswer == null) {
                 s_logger.error("Migration with storage of vm " + vm + " to host " + destHost + " failed.");
                 throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost);
@@ -150,16 +309,22 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
             MigrateWithStorageSendCommand sendCmd =
                     new MigrateWithStorageSendCommand(to, receiveAnswer.getVolumeToSr(), receiveAnswer.getNicToNetwork(), receiveAnswer.getToken());
             MigrateWithStorageSendAnswer sendAnswer = (MigrateWithStorageSendAnswer)agentMgr.send(srcHost.getId(), sendCmd);
+
             if (sendAnswer == null) {
+                handleManagedVolumesAfterFailedMigration(volumeToPool, destHost);
+
                 s_logger.error("Migration with storage of vm " + vm + " to host " + destHost + " failed.");
                 throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost);
             } else if (!sendAnswer.getResult()) {
+                handleManagedVolumesAfterFailedMigration(volumeToPool, destHost);
+
                 s_logger.error("Migration with storage of vm " + vm + " failed. Details: " + sendAnswer.getDetails());
                 throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost);
             }
 
             MigrateWithStorageCompleteCommand command = new MigrateWithStorageCompleteCommand(to);
             MigrateWithStorageCompleteAnswer answer = (MigrateWithStorageCompleteAnswer)agentMgr.send(destHost.getId(), command);
+
             if (answer == null) {
                 s_logger.error("Migration with storage of vm " + vm + " failed.");
                 throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost);
@@ -168,7 +333,7 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
                 throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost);
             } else {
                 // Update the volume details after migration.
-                updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos());
+                updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos(), srcHost);
             }
 
             return answer;
@@ -181,7 +346,7 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
     private Answer migrateVmWithVolumesWithinCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, Host destHost, Map<VolumeInfo, DataStore> volumeToPool)
             throws AgentUnavailableException {
 
-        // Initiate migration of a virtual machine with it's volumes.
+        // Initiate migration of a virtual machine with its volumes.
         try {
             List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerto = new ArrayList<Pair<VolumeTO, StorageFilerTO>>();
             for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
@@ -201,7 +366,7 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
                 throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + ". " + answer.getDetails());
             } else {
                 // Update the volume details after migration.
-                updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos());
+                updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos(), srcHost);
             }
 
             return answer;
@@ -211,28 +376,39 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy {
         }
     }
 
-    private void updateVolumePathsAfterMigration(Map<VolumeInfo, DataStore> volumeToPool, List<VolumeObjectTO> volumeTos) {
+    private void updateVolumePathsAfterMigration(Map<VolumeInfo, DataStore> volumeToPool, List<VolumeObjectTO> volumeTos, Host srcHost) {
         for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
+            VolumeInfo volumeInfo = entry.getKey();
+            StoragePool storagePool = (StoragePool)entry.getValue();
+
             boolean updated = false;
-            VolumeInfo volume = entry.getKey();
-            StoragePool pool = (StoragePool)entry.getValue();
+
             for (VolumeObjectTO volumeTo : volumeTos) {
-                if (volume.getId() == volumeTo.getId()) {
-                    VolumeVO volumeVO = volDao.findById(volume.getId());
-                    Long oldPoolId = volumeVO.getPoolId();
-                    volumeVO.setPath(volumeTo.getPath());
-                    volumeVO.setFolder(pool.getPath());
-                    volumeVO.setPodId(pool.getPodId());
-                    volumeVO.setPoolId(pool.getId());
-                    volumeVO.setLastPoolId(oldPoolId);
-                    volDao.update(volume.getId(), volumeVO);
+                if (volumeInfo.getId() == volumeTo.getId()) {
+                    if (storagePool.isManaged()) {
+                        handleManagedVolumePostMigration(volumeInfo, srcHost, volumeTo);
+                    }
+                    else {
+                        VolumeVO volumeVO = volDao.findById(volumeInfo.getId());
+                        Long oldPoolId = volumeVO.getPoolId();
+
+                        volumeVO.setPath(volumeTo.getPath());
+                        volumeVO.setFolder(storagePool.getPath());
+                        volumeVO.setPodId(storagePool.getPodId());
+                        volumeVO.setPoolId(storagePool.getId());
+                        volumeVO.setLastPoolId(oldPoolId);
+
+                        volDao.update(volumeInfo.getId(), volumeVO);
+                    }
+
                     updated = true;
+
                     break;
                 }
             }
 
             if (!updated) {
-                s_logger.error("Volume path wasn't updated for volume " + volume + " after it was migrated.");
+                s_logger.error("The volume path wasn't updated for volume '" + volumeInfo + "' after it was migrated.");
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
index f294af1..8fa68f5 100644
--- a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
+++ b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
@@ -204,9 +204,9 @@ public class XenServer610WrapperTest {
         final StorageFilerTO storage1 = Mockito.mock(StorageFilerTO.class);
         final StorageFilerTO storage2 = Mockito.mock(StorageFilerTO.class);
 
-        final List<Pair<VolumeTO, StorageFilerTO>> volumeToFiler = new ArrayList<Pair<VolumeTO, StorageFilerTO>>();
-        volumeToFiler.add(new Pair<VolumeTO, StorageFilerTO>(vol1, storage1));
-        volumeToFiler.add(new Pair<VolumeTO, StorageFilerTO>(vol2, storage2));
+        final List<Pair<VolumeTO, String>> volumeToFiler = new ArrayList<>();
+        volumeToFiler.add(new Pair<>(vol1, storage1.getPath()));
+        volumeToFiler.add(new Pair<>(vol2, storage2.getPath()));
 
         final NicTO nicTO1 = Mockito.mock(NicTO.class);
         final NicTO nicTO2 = Mockito.mock(NicTO.class);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
index af969e1..ccc1bdc 100644
--- a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
+++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
@@ -94,6 +94,8 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
     private static final long MIN_IOPS_FOR_SNAPSHOT_VOLUME = 100L;
     private static final long MAX_IOPS_FOR_SNAPSHOT_VOLUME = 20000L;
 
+    private static final String BASIC_SF_ID = "basicSfId";
+
     @Inject private AccountDao accountDao;
     @Inject private AccountDetailsDao accountDetailsDao;
     @Inject private ClusterDao clusterDao;
@@ -153,7 +155,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
         Preconditions.checkArgument(host != null, "'host' should not be 'null'");
         Preconditions.checkArgument(dataStore != null, "'dataStore' should not be 'null'");
 
-        long sfVolumeId = getSolidFireVolumeId(dataObject);
+        long sfVolumeId = getSolidFireVolumeId(dataObject, true);
         long clusterId = host.getClusterId();
         long storagePoolId = dataStore.getId();
 
@@ -215,7 +217,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
             return;
         }
 
-        long sfVolumeId = getSolidFireVolumeId(dataObject);
+        long sfVolumeId = getSolidFireVolumeId(dataObject, false);
         long clusterId = host.getClusterId();
         long storagePoolId = dataStore.getId();
 
@@ -252,9 +254,31 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
         }
     }
 
-    private long getSolidFireVolumeId(DataObject dataObject) {
+    private long getSolidFireVolumeId(DataObject dataObject, boolean grantAccess) {
         if (dataObject.getType() == DataObjectType.VOLUME) {
-            return Long.parseLong(((VolumeInfo)dataObject).getFolder());
+            final VolumeInfo volumeInfo = (VolumeInfo)dataObject;
+            final long volumeId = volumeInfo.getId();
+
+            if (grantAccess && isBasicGrantAccess(volumeId)) {
+                volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_GRANT_ACCESS);
+
+                final Long sfVolumeId = getBasicSfVolumeId(volumeId);
+
+                Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null' (basic grant access).");
+
+                return sfVolumeId;
+            }
+            else if (!grantAccess && isBasicRevokeAccess(volumeId)) {
+                volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_REVOKE_ACCESS);
+
+                final Long sfVolumeId = getBasicSfVolumeId(volumeId);
+
+                Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null' (basic revoke access).");
+
+                return sfVolumeId;
+            }
+
+            return Long.parseLong(volumeInfo.getFolder());
         }
 
         if (dataObject.getType() == DataObjectType.SNAPSHOT) {
@@ -271,7 +295,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
             return getVolumeIdFrom_iScsiPath(((TemplateInfo)dataObject).getInstallPath());
         }
 
-        throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to getSolidFireVolumeId(DataObject)");
+        throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to getSolidFireVolumeId(DataObject, boolean)");
     }
 
     private long getVolumeIdFrom_iScsiPath(String iScsiPath) {
@@ -313,10 +337,11 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
 
     private SolidFireUtil.SolidFireVolume createSolidFireVolume(SolidFireUtil.SolidFireConnection sfConnection, DataObject dataObject, long sfAccountId) {
         long storagePoolId = dataObject.getDataStore().getId();
-        Long minIops = null;
-        Long maxIops = null;
-        Long volumeSize = dataObject.getSize();
-        String volumeName = null;
+
+        final Long minIops;
+        final Long maxIops;
+        final Long volumeSize;
+        final String volumeName;
 
         final Map<String, String> mapAttributes;
 
@@ -647,6 +672,58 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
         snapshotDetailsDao.remove(snapshotDetails.getId());
     }
 
+    private Long getBasicSfVolumeId(long volumeId) {
+        VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, BASIC_SF_ID);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return new Long(volumeDetail.getValue());
+        }
+
+        return null;
+    }
+
+    private String getBasicIqn(long volumeId) {
+        VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, BASIC_IQN);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return volumeDetail.getValue();
+        }
+
+        return null;
+    }
+
+    // If isBasicCreate returns true, this means the calling code simply wants us to create a SolidFire volume with specified
+    // characteristics. We do not update the cloud.volumes table with this info.
+    private boolean isBasicCreate(long volumeId) {
+        return getBooleanValueFromVolumeDetails(volumeId, BASIC_CREATE);
+    }
+
+    private boolean isBasicDelete(long volumeId) {
+        return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE);
+    }
+
+    private boolean isBasicDeleteFailure(long volumeId) {
+        return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE_FAILURE);
+    }
+
+    private boolean isBasicGrantAccess(long volumeId) {
+        return getBooleanValueFromVolumeDetails(volumeId, BASIC_GRANT_ACCESS);
+    }
+
+    private boolean isBasicRevokeAccess(long volumeId) {
+        return getBooleanValueFromVolumeDetails(volumeId, BASIC_REVOKE_ACCESS);
+    }
+
+    private boolean getBooleanValueFromVolumeDetails(long volumeId, String name) {
+        VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, name);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Boolean.parseBoolean(volumeDetail.getValue());
+        }
+
+        return false;
+    }
+
     private long getCsIdForCloning(long volumeId, String cloneOf) {
         VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, cloneOf);
 
@@ -788,11 +865,13 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
             LOGGER.error(errMsg);
         }
 
-        CommandResult result = new CommandResult();
+        if (callback != null) {
+            CommandResult result = new CommandResult();
 
-        result.setResult(errMsg);
+            result.setResult(errMsg);
 
-        callback.complete(result);
+            callback.complete(result);
+        }
     }
 
     @Override
@@ -950,19 +1029,43 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
         snapshotDetailsDao.persist(snapshotDetail);
     }
 
+    private void addBasicCreateInfoToVolumeDetails(long volumeId, SolidFireUtil.SolidFireVolume sfVolume) {
+        VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeId, BASIC_SF_ID, String.valueOf(sfVolume.getId()), false);
+
+        volumeDetailsDao.persist(volumeDetailVo);
+
+        volumeDetailVo = new VolumeDetailVO(volumeId, BASIC_IQN, sfVolume.getIqn(), false);
+
+        volumeDetailsDao.persist(volumeDetailVo);
+    }
+
     private String createVolume(VolumeInfo volumeInfo, long storagePoolId) {
-        verifySufficientBytesForStoragePool(volumeInfo, storagePoolId);
-        verifySufficientIopsForStoragePool(volumeInfo.getMinIops() != null ? volumeInfo.getMinIops() : getDefaultMinIops(storagePoolId), storagePoolId);
+        boolean isBasicCreate = isBasicCreate(volumeInfo.getId());
+
+        if (!isBasicCreate) {
+            verifySufficientBytesForStoragePool(volumeInfo, storagePoolId);
+            verifySufficientIopsForStoragePool(volumeInfo.getMinIops() != null ? volumeInfo.getMinIops() : getDefaultMinIops(storagePoolId), storagePoolId);
+        }
 
         SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao);
 
         long sfAccountId = getCreateSolidFireAccountId(sfConnection, volumeInfo.getAccountId(), storagePoolId);
 
+        SolidFireUtil.SolidFireVolume sfVolume;
+
+        if (isBasicCreate) {
+            sfVolume = createSolidFireVolume(sfConnection, volumeInfo, sfAccountId);
+
+            volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_CREATE);
+
+            addBasicCreateInfoToVolumeDetails(volumeInfo.getId(), sfVolume);
+
+            return sfVolume.getIqn();
+        }
+
         long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
         long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
 
-        SolidFireUtil.SolidFireVolume sfVolume;
-
         if (csSnapshotId > 0) {
             // We are supposed to create a clone of the underlying volume or snapshot that supports the CloudStack snapshot.
             sfVolume = createClone(sfConnection, csSnapshotId, volumeInfo, sfAccountId, storagePoolId, DataObjectType.SNAPSHOT);
@@ -1083,23 +1186,66 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
         return iqn;
     }
 
+    private void performBasicDelete(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) {
+        Long sfVolumeId = getBasicSfVolumeId(volumeId);
+
+        Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null'.");
+
+        String iqn = getBasicIqn(volumeId);
+
+        Preconditions.checkNotNull(iqn, "'iqn' should not be 'null'.");
+
+        VolumeVO volumeVO = volumeDao.findById(volumeId);
+
+        SolidFireUtil.deleteSolidFireVolume(sfConnection, Long.parseLong(volumeVO.getFolder()));
+
+        volumeVO.setFolder(String.valueOf(sfVolumeId));
+        volumeVO.set_iScsiName(iqn);
+
+        volumeDao.update(volumeId, volumeVO);
+
+        volumeDetailsDao.removeDetail(volumeId, BASIC_SF_ID);
+        volumeDetailsDao.removeDetail(volumeId, BASIC_IQN);
+        volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE);
+    }
+
+    private void performBasicDeleteFailure(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) {
+        Long sfVolumeId = getBasicSfVolumeId(volumeId);
+
+        Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null'.");
+
+        SolidFireUtil.deleteSolidFireVolume(sfConnection, sfVolumeId);
+
+        volumeDetailsDao.removeDetail(volumeId, BASIC_SF_ID);
+        volumeDetailsDao.removeDetail(volumeId, BASIC_IQN);
+        volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE_FAILURE);
+    }
+
     private void deleteVolume(VolumeInfo volumeInfo, long storagePoolId) {
         try {
             long volumeId = volumeInfo.getId();
 
             SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao);
 
-            deleteSolidFireVolume(sfConnection, volumeInfo);
+            if (isBasicDelete(volumeId)) {
+                performBasicDelete(sfConnection, volumeId);
+            }
+            else if (isBasicDeleteFailure(volumeId)) {
+                performBasicDeleteFailure(sfConnection, volumeId);
+            }
+            else {
+                deleteSolidFireVolume(sfConnection, volumeInfo);
 
-            volumeDetailsDao.removeDetails(volumeId);
+                volumeDetailsDao.removeDetails(volumeId);
 
-            StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
+                StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
 
-            long usedBytes = getUsedBytes(storagePool, volumeId);
+                long usedBytes = getUsedBytes(storagePool, volumeId);
 
-            storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
+                storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
 
-            storagePoolDao.update(storagePoolId, storagePool);
+                storagePoolDao.update(storagePoolId, storagePool);
+            }
         }
         catch (Exception ex) {
             LOGGER.debug(SolidFireUtil.LOG_PREFIX + "Failed to delete SolidFire volume. CloudStack volume ID: " + volumeInfo.getId(), ex);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/server/src/com/cloud/server/ManagementServerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java
index 60b44d7..82f8030 100644
--- a/server/src/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/com/cloud/server/ManagementServerImpl.java
@@ -1205,12 +1205,15 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
                     srcHost.getHypervisorType(), srcHost.getHypervisorVersion());
             allHosts = allHostsPair.first();
             allHosts.remove(srcHost);
+
             for (final VolumeVO volume : volumes) {
-                final Long volClusterId = _poolDao.findById(volume.getPoolId()).getClusterId();
-                // only check for volume which are not in zone wide primary store, as only those may require storage motion
-                if (volClusterId != null) {
-                    for (final Iterator<HostVO> iterator = allHosts.iterator(); iterator.hasNext();) {
-                        final Host host = iterator.next();
+                final StoragePool storagePool = _poolDao.findById(volume.getPoolId());
+                final Long volClusterId = storagePool.getClusterId();
+
+                for (final Iterator<HostVO> iterator = allHosts.iterator(); iterator.hasNext();) {
+                    final Host host = iterator.next();
+
+                    if (volClusterId != null) {
                         if (!host.getClusterId().equals(volClusterId) || usesLocal) {
                             if (hasSuitablePoolsForVolume(volume, host, vmProfile)) {
                                 requiresStorageMotion.put(host, true);
@@ -1219,8 +1222,16 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
                             }
                         }
                     }
+                    else {
+                        if (storagePool.isManaged()) {
+                            if (srcHost.getClusterId() != host.getClusterId()) {
+                                requiresStorageMotion.put(host, true);
+                            }
+                        }
+                    }
                 }
             }
+
             plan = new DataCenterDeployment(srcHost.getDataCenterId(), null, null, null, null, null);
         } else {
             final Long cluster = srcHost.getClusterId();
@@ -1249,7 +1260,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
         }
 
         for (final HostAllocator allocator : hostAllocators) {
-            if  (canMigrateWithStorage) {
+            if (canMigrateWithStorage) {
                 suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, allHosts, HostAllocator.RETURN_UPTO_ALL, false);
             } else {
                 suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, HostAllocator.RETURN_UPTO_ALL, false);

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/test/integration/plugins/solidfire/TestAddRemoveHosts.py
----------------------------------------------------------------------
diff --git a/test/integration/plugins/solidfire/TestAddRemoveHosts.py b/test/integration/plugins/solidfire/TestAddRemoveHosts.py
index 518d022..a13c61a 100644
--- a/test/integration/plugins/solidfire/TestAddRemoveHosts.py
+++ b/test/integration/plugins/solidfire/TestAddRemoveHosts.py
@@ -21,6 +21,8 @@ import SignedAPICall
 import time
 import XenAPI
 
+from util import sf_util
+
 # All tests inherit from cloudstackTestCase
 from marvin.cloudstackTestCase import cloudstackTestCase
 
@@ -37,6 +39,15 @@ from marvin.lib.utils import cleanup_resources
 
 from solidfire import solidfire_element_api as sf_api
 
+# Prerequisites:
+#  Only one zone
+#  Only one pod
+#  Only one cluster (two hosts with another added/removed during the tests)
+#
+# Running the tests:
+#  Set a breakpoint on each test after the first one. When the breakpoint is hit, reset the third
+#   host to a snapshot state and re-start it. Once it's up and running, run the test code.
+
 
 class TestData:
     account = "account"
@@ -238,7 +249,7 @@ class TestAddRemoveHosts(cloudstackTestCase):
         try:
             cleanup_resources(cls.apiClient, cls._cleanup)
 
-            cls._purge_solidfire_volumes()
+            sf_util.purge_solidfire_volumes(cls.sf_client)
         except Exception as e:
             logging.debug("Exception in tearDownClass(cls): %s" % e)
 
@@ -286,7 +297,7 @@ class TestAddRemoveHosts(cloudstackTestCase):
 
         root_volume = self._get_root_volume(self.virtual_machine)
 
-        sf_iscsi_name = self._get_iqn(root_volume)
+        sf_iscsi_name = sf_util.get_iqn(self.cs_api, root_volume, self)
 
         self._perform_add_remove_host(primary_storage.id, sf_iscsi_name)
 
@@ -342,7 +353,7 @@ class TestAddRemoveHosts(cloudstackTestCase):
 
         root_volume = self._get_root_volume(self.virtual_machine)
 
-        sf_iscsi_name = self._get_iqn(root_volume)
+        sf_iscsi_name = sf_util.get_iqn(self.cs_api, root_volume, self)
 
         primarystorage2 = self.testdata[TestData.primaryStorage2]
 
@@ -596,19 +607,6 @@ class TestAddRemoveHosts(cloudstackTestCase):
 
         self.assert_(False, "Unable to locate the ROOT volume of the VM with the following ID: " + str(vm.id))
 
-    def _get_iqn(self, volume):
-        # Get volume IQN
-        sf_iscsi_name_request = {'volumeid': volume.id}
-        # put this commented line back once PR 1403 is in
-        # sf_iscsi_name_result = self.cs_api.getVolumeiScsiName(sf_iscsi_name_request)
-        sf_iscsi_name_result = self.cs_api.getSolidFireVolumeIscsiName(sf_iscsi_name_request)
-        # sf_iscsi_name = sf_iscsi_name_result['apivolumeiscsiname']['volumeiScsiName']
-        sf_iscsi_name = sf_iscsi_name_result['apisolidfirevolumeiscsiname']['solidFireVolumeIscsiName']
-
-        self._check_iscsi_name(sf_iscsi_name)
-
-        return sf_iscsi_name
-
     def _get_iqn_2(self, primary_storage):
         sql_query = "Select path From storage_pool Where uuid = '" + str(primary_storage.id) + "'"
 
@@ -617,13 +615,6 @@ class TestAddRemoveHosts(cloudstackTestCase):
 
         return sql_result[0][0]
 
-    def _check_iscsi_name(self, sf_iscsi_name):
-        self.assertEqual(
-            sf_iscsi_name[0],
-            "/",
-            "The iSCSI name needs to start with a forward slash."
-        )
-
     def _get_host_iscsi_iqns(self):
         hosts = self.xen_session.xenapi.host.get_all()
 
@@ -687,24 +678,3 @@ class TestAddRemoveHosts(cloudstackTestCase):
         for host_iscsi_iqn in host_iscsi_iqns:
             # an error should occur if host_iscsi_iqn is not in sf_vag_initiators
             sf_vag_initiators.index(host_iscsi_iqn)
-
-    def _check_list(self, in_list, expected_size_of_list, err_msg):
-        self.assertEqual(
-            isinstance(in_list, list),
-            True,
-            "'in_list' is not a list."
-        )
-
-        self.assertEqual(
-            len(in_list),
-            expected_size_of_list,
-            err_msg
-        )
-
-    @classmethod
-    def _purge_solidfire_volumes(cls):
-        deleted_volumes = cls.sf_client.list_deleted_volumes()
-
-        for deleted_volume in deleted_volumes:
-            cls.sf_client.purge_deleted_volume(deleted_volume['volumeID'])
-