You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by GitBox <gi...@apache.org> on 2018/01/06 08:55:34 UTC

[GitHub] DaanHoogland closed pull request #2315: A comprehensive solution for CLOUDSTACK-9025 and CLOUDSTACK-10128.

DaanHoogland closed pull request #2315: A comprehensive solution for CLOUDSTACK-9025 and CLOUDSTACK-10128.
URL: https://github.com/apache/cloudstack/pull/2315
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/api/src/com/cloud/hypervisor/HypervisorGuru.java b/api/src/com/cloud/hypervisor/HypervisorGuru.java
index 6a09dd257af..45e19ee2674 100644
--- a/api/src/com/cloud/hypervisor/HypervisorGuru.java
+++ b/api/src/com/cloud/hypervisor/HypervisorGuru.java
@@ -33,7 +33,7 @@
 
 public interface HypervisorGuru extends Adapter {
     static final ConfigKey<Boolean> VmwareFullClone = new ConfigKey<Boolean>("Advanced", Boolean.class, "vmware.create.full.clone", "true",
-                        "If set to true, creates guest VMs as full clones on ESX", false);
+            "If set to true, creates guest VMs as full clones on ESX", false);
     HypervisorType getHypervisorType();
 
     /**
@@ -45,7 +45,7 @@
     VirtualMachineTO implement(VirtualMachineProfile vm);
 
     /**
-     * Give hypervisor guru opportunity to decide if certain command needs to be delegated to other host, mainly to secondary storage VM host
+     * Gives hypervisor guru opportunity to decide if certain commands need to be delegated to another host, for instance, we may have the opportunity to change from a system VM (is considered a host) to a real host to execute commands.
      *
      * @param hostId original hypervisor host
      * @param cmd command that is going to be sent, hypervisor guru usually needs to register various context objects into the command object
diff --git a/core/src/org/apache/cloudstack/storage/command/CopyCommand.java b/core/src/org/apache/cloudstack/storage/command/CopyCommand.java
index f1895a4ae51..e7ebab812c1 100644
--- a/core/src/org/apache/cloudstack/storage/command/CopyCommand.java
+++ b/core/src/org/apache/cloudstack/storage/command/CopyCommand.java
@@ -24,7 +24,7 @@
 
 import com.cloud.agent.api.to.DataTO;
 
-public final class CopyCommand extends StorageSubSystemCommand {
+public class CopyCommand extends StorageSubSystemCommand {
     private DataTO srcTO;
     private DataTO destTO;
     private DataTO cacheTO;
diff --git a/engine/schema/src/com/cloud/host/dao/HostDao.java b/engine/schema/src/com/cloud/host/dao/HostDao.java
index f98e8c19eeb..9e967e34efd 100644
--- a/engine/schema/src/com/cloud/host/dao/HostDao.java
+++ b/engine/schema/src/com/cloud/host/dao/HostDao.java
@@ -19,6 +19,8 @@
 import java.util.Date;
 import java.util.List;
 
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+
 import com.cloud.host.Host;
 import com.cloud.host.Host.Type;
 import com.cloud.host.HostVO;
@@ -72,14 +74,6 @@
 
     List<HostVO> findHypervisorHostInCluster(long clusterId);
 
-    /**
-     * @param type
-     * @param clusterId
-     * @param podId
-     * @param dcId
-     * @param haTag TODO
-     * @return
-     */
     List<HostVO> listAllUpAndEnabledNonHAHosts(Type type, Long clusterId, Long podId, long dcId, String haTag);
 
     List<HostVO> findByDataCenterId(Long zoneId);
@@ -103,4 +97,16 @@
     List<HostVO> listByType(Type type);
 
     HostVO findByIp(String ip);
+
+    /**
+     * This method will look for a host that is of the same hypervisor and same zone as the storage pool where the volume of the Snapshot is stored.
+     * <ul>
+     * <li>(this is applicable only for XenServer) If the storage pool is managed, then we will look for a host that has the property 'supportsResign' in cloud.cluster_details
+     * <li>We give priority to 'Enabled' hosts, but if no 'Enabled' hosts are found, we use 'Disabled' hosts
+     * <li>If no host is found, we throw a runtime exception
+     * </ul>
+     *
+     * Side note: this method is currently only used in XenServerGuru; therefore, it was designed to meet XenServer deployment scenarios requirements.
+     */
+    HostVO findHostToOperateOnSnapshotBasedOnStoragePool(StoragePoolVO storagePoolVO);
 }
diff --git a/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java
index 8c8ca8cc749..c1403d08609 100644
--- a/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java
+++ b/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java
@@ -30,13 +30,15 @@
 import javax.inject.Inject;
 import javax.persistence.TableGenerator;
 
-import com.cloud.configuration.ManagementServiceConfiguration;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import com.cloud.agent.api.VgpuTypesInfo;
 import com.cloud.cluster.agentlb.HostTransferMapVO;
 import com.cloud.cluster.agentlb.dao.HostTransferMapDao;
+import com.cloud.configuration.ManagementServiceConfiguration;
 import com.cloud.dc.ClusterVO;
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.gpu.dao.HostGpuGroupsDao;
@@ -305,11 +307,11 @@ public void init() {
         }
         HostTransferSearch.and("id", HostTransferSearch.entity().getId(), SearchCriteria.Op.NULL);
         UnmanagedDirectConnectSearch.join("hostTransferSearch", HostTransferSearch, HostTransferSearch.entity().getId(), UnmanagedDirectConnectSearch.entity().getId(),
-            JoinType.LEFTOUTER);
+                JoinType.LEFTOUTER);
         ClusterManagedSearch = _clusterDao.createSearchBuilder();
         ClusterManagedSearch.and("managed", ClusterManagedSearch.entity().getManagedState(), SearchCriteria.Op.EQ);
-        UnmanagedDirectConnectSearch.join("ClusterManagedSearch", ClusterManagedSearch, ClusterManagedSearch.entity().getId(), UnmanagedDirectConnectSearch.entity()
-            .getClusterId(), JoinType.INNER);
+        UnmanagedDirectConnectSearch.join("ClusterManagedSearch", ClusterManagedSearch, ClusterManagedSearch.entity().getId(), UnmanagedDirectConnectSearch.entity().getClusterId(),
+                JoinType.INNER);
         UnmanagedDirectConnectSearch.done();
 
         DirectConnectSearch = createSearchBuilder();
@@ -388,7 +390,8 @@ public void init() {
 
         ClusterManagedSearch = _clusterDao.createSearchBuilder();
         ClusterManagedSearch.and("managed", ClusterManagedSearch.entity().getManagedState(), SearchCriteria.Op.EQ);
-        ClustersForHostsNotOwnedByAnyMSSearch.join("ClusterManagedSearch", ClusterManagedSearch, ClusterManagedSearch.entity().getId(), ClustersForHostsNotOwnedByAnyMSSearch.entity().getClusterId(), JoinType.INNER);
+        ClustersForHostsNotOwnedByAnyMSSearch.join("ClusterManagedSearch", ClusterManagedSearch, ClusterManagedSearch.entity().getId(),
+                ClustersForHostsNotOwnedByAnyMSSearch.entity().getClusterId(), JoinType.INNER);
 
         ClustersForHostsNotOwnedByAnyMSSearch.done();
 
@@ -520,18 +523,7 @@ private void resetHosts(long managementServerId, long lastPingSecondsAfter) {
         return clusters;
     }
 
-    /*
-     * Returns a list of all cluster Ids
-     */
-    private List<Long> listAllClusters() {
-        SearchCriteria<Long> sc = AllClustersSearch.create();
-        sc.setParameters("managed", Managed.ManagedState.Managed);
-
-        List<Long> clusters = _clusterDao.customSearch(sc, null);
-        return clusters;
-    }
-
-    /*
+    /**
      * This determines if hosts belonging to cluster(@clusterId) are up for grabs
      *
      * This is used for handling following cases:
@@ -656,7 +648,7 @@ private boolean canOwnCluster(long clusterId) {
         SearchCriteria<HostVO> sc = UnmanagedApplianceSearch.create();
         sc.setParameters("lastPinged", lastPingSecondsAfter);
         sc.setParameters("types", Type.ExternalDhcp, Type.ExternalFirewall, Type.ExternalLoadBalancer, Type.BaremetalDhcp, Type.BaremetalPxe, Type.TrafficMonitor,
-            Type.L2Networking, Type.NetScalerControlCenter);
+                Type.L2Networking, Type.NetScalerControlCenter);
         List<HostVO> hosts = lockRows(sc, null, true);
 
         for (HostVO host : hosts) {
@@ -790,10 +782,8 @@ public void loadHostTags(HostVO host) {
     @Override
     public List<HostVO> findLostHosts(long timeout) {
         List<HostVO> result = new ArrayList<HostVO>();
-        String sql =
-                "select h.id from host h left join  cluster c on h.cluster_id=c.id where h.mgmt_server_id is not null and h.last_ping < ? and h.status in ('Up', 'Updating', 'Disconnected', 'Connecting') and h.type not in ('ExternalFirewall', 'ExternalLoadBalancer', 'TrafficMonitor', 'SecondaryStorage', 'LocalSecondaryStorage', 'L2Networking') and (h.cluster_id is null or c.managed_state = 'Managed') ;";
-        try (
-                TransactionLegacy txn = TransactionLegacy.currentTxn();
+        String sql = "select h.id from host h left join  cluster c on h.cluster_id=c.id where h.mgmt_server_id is not null and h.last_ping < ? and h.status in ('Up', 'Updating', 'Disconnected', 'Connecting') and h.type not in ('ExternalFirewall', 'ExternalLoadBalancer', 'TrafficMonitor', 'SecondaryStorage', 'LocalSecondaryStorage', 'L2Networking') and (h.cluster_id is null or c.managed_state = 'Managed') ;";
+        try (TransactionLegacy txn = TransactionLegacy.currentTxn();
                 PreparedStatement pstmt = txn.prepareStatement(sql);) {
             pstmt.setLong(1, timeout);
             try (ResultSet rs = pstmt.executeQuery();) {
@@ -887,8 +877,7 @@ public boolean update(Long hostId, HostVO host) {
     @Override
     @DB
     public List<RunningHostCountInfo> getRunningHostCounts(Date cutTime) {
-        String sql =
-            "select * from (" + "select h.data_center_id, h.type, count(*) as count from host as h INNER JOIN mshost as m ON h.mgmt_server_id=m.msid "
+        String sql = "select * from (" + "select h.data_center_id, h.type, count(*) as count from host as h INNER JOIN mshost as m ON h.mgmt_server_id=m.msid "
                 + "where h.status='Up' and h.type='SecondaryStorage' and m.last_update > ? " + "group by h.data_center_id, h.type " + "UNION ALL "
                 + "select h.data_center_id, h.type, count(*) as count from host as h INNER JOIN mshost as m ON h.mgmt_server_id=m.msid "
                 + "where h.status='Up' and h.type='Routing' and m.last_update > ? " + "group by h.data_center_id, h.type) as t " + "ORDER by t.data_center_id, t.type";
@@ -1006,24 +995,12 @@ public boolean updateState(Status oldStatus, Event event, Status newStatus, Host
 
                 StringBuilder str = new StringBuilder("Unable to update host for event:").append(event.toString());
                 str.append(". Name=").append(host.getName());
-                str.append("; New=[status=")
-                        .append(newStatus.toString())
-                        .append(":msid=")
-                        .append(newStatus.lostConnection() ? "null" : host.getManagementServerId())
-                        .append(":lastpinged=")
-                        .append(host.getLastPinged())
-                        .append("]");
+                str.append("; New=[status=").append(newStatus.toString()).append(":msid=").append(newStatus.lostConnection() ? "null" : host.getManagementServerId())
+                .append(":lastpinged=").append(host.getLastPinged()).append("]");
                 str.append("; Old=[status=").append(oldStatus.toString()).append(":msid=").append(host.getManagementServerId()).append(":lastpinged=").append(oldPingTime)
-                        .append("]");
-                str.append("; DB=[status=")
-                        .append(vo.getStatus().toString())
-                        .append(":msid=")
-                        .append(vo.getManagementServerId())
-                        .append(":lastpinged=")
-                        .append(vo.getLastPinged())
-                        .append(":old update count=")
-                        .append(oldUpdateCount)
-                        .append("]");
+                .append("]");
+                str.append("; DB=[status=").append(vo.getStatus().toString()).append(":msid=").append(vo.getManagementServerId()).append(":lastpinged=").append(vo.getLastPinged())
+                .append(":old update count=").append(oldUpdateCount).append("]");
                 status_logger.debug(str.toString());
             } else {
                 StringBuilder msg = new StringBuilder("Agent status update: [");
@@ -1190,4 +1167,48 @@ public HostVO findByIp(final String ipAddress) {
         sc.setParameters("type", type);
         return listBy(sc);
     }
+
+    String sqlFindHostConnectedToStoragePoolToExecuteCommand = "select h.id from storage_pool pool "
+            + " join cluster c on pool.cluster_id = c.id "
+            + " %s "
+            + " join host h on h.data_center_id = c.data_center_id and h.hypervisor_type = c.hypervisor_type"
+            + " where pool.id = ? and h.status = 'Up' and h.type = 'Routing' and resource_state = '%s' "
+            + " ORDER by rand() limit 1 ";
+
+    @Override
+    public HostVO findHostToOperateOnSnapshotBasedOnStoragePool(StoragePoolVO storagePoolVO) {
+        try (TransactionLegacy tx = TransactionLegacy.currentTxn()) {
+            String sql = createSqlFindHostConnectedToStoragePoolToExecuteCommand(storagePoolVO, false);
+            ResultSet rs = executeSqlGetResultSetForMethodFindHostToOperateBasedOnStoragePool(storagePoolVO, tx, sql);
+            if (rs.next()) {
+                return findById(rs.getLong("id"));
+            }
+            sql = createSqlFindHostConnectedToStoragePoolToExecuteCommand(storagePoolVO, true);
+            rs = executeSqlGetResultSetForMethodFindHostToOperateBasedOnStoragePool(storagePoolVO, tx, sql);
+            if (!rs.next()) {
+                throw new CloudRuntimeException(String.format("Could not find a host connected to the storage pool [storagepool=%d]. ", storagePoolVO.getId()));
+            }
+            return findById(rs.getLong("id"));
+        } catch (SQLException e) {
+            throw new CloudRuntimeException(e);
+        }
+    }
+
+    private ResultSet executeSqlGetResultSetForMethodFindHostToOperateBasedOnStoragePool(StoragePoolVO storagePoolVO, TransactionLegacy tx, String sql) throws SQLException {
+        PreparedStatement pstmt = tx.prepareAutoCloseStatement(sql);
+        pstmt.setLong(1, storagePoolVO.getId());
+        return pstmt.executeQuery();
+    }
+
+    private String createSqlFindHostConnectedToStoragePoolToExecuteCommand(StoragePoolVO storagePoolVO, boolean useDisabledHosts) {
+        String hostResourceStatus = "Enabled";
+        if (useDisabledHosts) {
+            hostResourceStatus = "Disabled";
+        }
+        String joinForManagedStorage = StringUtils.EMPTY;
+        if (storagePoolVO.isManaged()) {
+            joinForManagedStorage = " join cluster_details cd on cd.cluster_id = c.id and cd.name = 'supportsResign' and cd.value = 'true' ";
+        }
+        return String.format(sqlFindHostConnectedToStoragePoolToExecuteCommand, joinForManagedStorage, hostResourceStatus);
+    }
 }
diff --git a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java
index 2398e91c90c..e665906c289 100644
--- a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java
+++ b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java
@@ -123,4 +123,11 @@
     List<StoragePoolVO> listLocalStoragePoolByPath(long datacenterId, String path);
 
     void deletePoolTags(long poolId);
+
+    /**
+     *  Looks for a storage pool where the original volume of the snapshot was taken.
+     *  Even if the volume has already been deleted, we will return the last storage pool where it was stored.
+     */
+    StoragePoolVO findStoragePoolForSnapshot(long snapshotId);
+
 }
diff --git a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java
index 5a3c229a94c..6fd4808f7d4 100644
--- a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java
+++ b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java
@@ -50,33 +50,29 @@
 
 @DB()
 public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long> implements PrimaryDataStoreDao {
-    protected final SearchBuilder<StoragePoolVO> AllFieldSearch;
-    protected final SearchBuilder<StoragePoolVO> DcPodSearch;
-    protected final SearchBuilder<StoragePoolVO> DcPodAnyClusterSearch;
-    protected final SearchBuilder<StoragePoolVO> DeleteLvmSearch;
-    protected final SearchBuilder<StoragePoolVO> DcLocalStorageSearch;
-    protected final GenericSearchBuilder<StoragePoolVO, Long> StatusCountSearch;
+    private final SearchBuilder<StoragePoolVO> AllFieldSearch;
+    private final SearchBuilder<StoragePoolVO> DcPodSearch;
+    private final SearchBuilder<StoragePoolVO> DcPodAnyClusterSearch;
+    private final SearchBuilder<StoragePoolVO> DeleteLvmSearch;
+    private final SearchBuilder<StoragePoolVO> DcLocalStorageSearch;
+    private final GenericSearchBuilder<StoragePoolVO, Long> StatusCountSearch;
 
     @Inject
-    protected StoragePoolDetailsDao _detailsDao;
+    private StoragePoolDetailsDao _detailsDao;
     @Inject
-    protected StoragePoolHostDao _hostDao;
+    private StoragePoolHostDao _hostDao;
     @Inject
-    protected StoragePoolTagsDao _tagsDao;
+    private StoragePoolTagsDao _tagsDao;
 
-    protected final String DetailsSqlPrefix =
-        "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_details ON storage_pool.id = storage_pool_details.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and (";
+    protected final String DetailsSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_details ON storage_pool.id = storage_pool_details.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and (";
     protected final String DetailsSqlSuffix = ") GROUP BY storage_pool_details.pool_id HAVING COUNT(storage_pool_details.name) >= ?";
-    protected final String ZoneWideTagsSqlPrefix =
-        "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_tags ON storage_pool.id = storage_pool_tags.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and storage_pool.scope = ? and (";
-    protected final String ZoneWideTagsSqlSuffix = ") GROUP BY storage_pool_tags.pool_id HAVING COUNT(storage_pool_tags.tag) >= ?";
+    private final String ZoneWideTagsSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_tags ON storage_pool.id = storage_pool_tags.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and storage_pool.scope = ? and (";
+    private final String ZoneWideTagsSqlSuffix = ") GROUP BY storage_pool_tags.pool_id HAVING COUNT(storage_pool_tags.tag) >= ?";
 
     // Storage tags are now separate from storage_pool_details, leaving only details on that table
     protected final String TagsSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_tags ON storage_pool.id = storage_pool_tags.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and (";
     protected final String TagsSqlSuffix = ") GROUP BY storage_pool_tags.pool_id HAVING COUNT(storage_pool_tags.tag) >= ?";
 
-    protected final String FindPoolTags = "SELECT storage_pool_tags.tag FROM storage_pool_tags WHERE pool_id = ?";
-
     /**
      * Used in method findPoolsByDetailsOrTagsInternal
      */
@@ -300,7 +296,8 @@ public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, Li
      * @param valuesLength values length
      * @return list of storage pools matching conditions
      */
-    protected List<StoragePoolVO> findPoolsByDetailsOrTagsInternal(long dcId, long podId, Long clusterId, ScopeType scope, String sqlValues, ValueType valuesType, int valuesLength) {
+    protected List<StoragePoolVO> findPoolsByDetailsOrTagsInternal(long dcId, long podId, Long clusterId, ScopeType scope, String sqlValues, ValueType valuesType,
+            int valuesLength) {
         String sqlPrefix = valuesType.equals(ValueType.DETAILS) ? DetailsSqlPrefix : TagsSqlPrefix;
         String sqlSuffix = valuesType.equals(ValueType.DETAILS) ? DetailsSqlSuffix : TagsSqlSuffix;
         String sql = getSqlPreparedStatement(sqlPrefix, sqlSuffix, sqlValues, clusterId);
@@ -321,7 +318,7 @@ public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, Li
     protected List<StoragePoolVO> searchStoragePoolsPreparedStatement(String sql, long dcId, Long podId, Long clusterId, ScopeType scope, int valuesLength) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         List<StoragePoolVO> pools = new ArrayList<StoragePoolVO>();
-        try (PreparedStatement pstmt = txn.prepareStatement(sql);){
+        try (PreparedStatement pstmt = txn.prepareStatement(sql);) {
             if (pstmt != null) {
                 int i = 1;
                 pstmt.setLong(i++, dcId);
@@ -333,11 +330,11 @@ public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, Li
                     pstmt.setLong(i++, clusterId);
                 }
                 pstmt.setInt(i++, valuesLength);
-                try(ResultSet rs = pstmt.executeQuery();) {
+                try (ResultSet rs = pstmt.executeQuery();) {
                     while (rs.next()) {
                         pools.add(toEntityBean(rs, false));
                     }
-                }catch (SQLException e) {
+                } catch (SQLException e) {
                     throw new CloudRuntimeException("Unable to execute :" + e.getMessage(), e);
                 }
             }
@@ -347,14 +344,6 @@ public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, Li
         return pools;
     }
 
-    /**
-     * Return SQL prepared statement given prefix, values and suffix
-     * @param sqlPrefix prefix
-     * @param sqlSuffix suffix
-     * @param sqlValues tags or details values
-     * @param clusterId cluster id
-     * @return sql prepared statement
-     */
     protected String getSqlPreparedStatement(String sqlPrefix, String sqlSuffix, String sqlValues, Long clusterId) {
         StringBuilder sql = new StringBuilder(sqlPrefix);
         if (clusterId != null) {
@@ -375,11 +364,7 @@ protected String getSqlPreparedStatement(String sqlPrefix, String sqlSuffix, Str
     protected String getSqlValuesFromDetails(Map<String, String> details) {
         StringBuilder sqlValues = new StringBuilder();
         for (Map.Entry<String, String> detail : details.entrySet()) {
-            sqlValues.append("((storage_pool_details.name='")
-                .append(detail.getKey())
-                .append("') AND (storage_pool_details.value='")
-                .append(detail.getValue())
-                .append("')) OR ");
+            sqlValues.append("((storage_pool_details.name='").append(detail.getKey()).append("') AND (storage_pool_details.value='").append(detail.getValue()).append("')) OR ");
         }
         sqlValues.delete(sqlValues.length() - 4, sqlValues.length());
         return sqlValues.toString();
@@ -395,9 +380,7 @@ protected String getSqlValuesFromDetails(Map<String, String> details) {
     protected String getSqlValuesFromStorageTags(String[] tags) throws NullPointerException, IndexOutOfBoundsException {
         StringBuilder sqlValues = new StringBuilder();
         for (String tag : tags) {
-            sqlValues.append("(storage_pool_tags.tag='")
-                .append(tag)
-                .append("') OR ");
+            sqlValues.append("(storage_pool_tags.tag='").append(tag).append("') OR ");
         }
         sqlValues.delete(sqlValues.length() - 4, sqlValues.length());
         return sqlValues.toString();
@@ -460,7 +443,8 @@ protected String getSqlValuesFromStorageTags(String[] tags) throws NullPointerEx
     public List<StoragePoolVO> findLocalStoragePoolsByHostAndTags(long hostId, String[] tags) {
         SearchBuilder<StoragePoolVO> hostSearch = createSearchBuilder();
         SearchBuilder<StoragePoolHostVO> hostPoolSearch = _hostDao.createSearchBuilder();
-        SearchBuilder<StoragePoolTagVO> tagPoolSearch = _tagsDao.createSearchBuilder();;
+        SearchBuilder<StoragePoolTagVO> tagPoolSearch = _tagsDao.createSearchBuilder();
+        ;
 
         // Search for pools on the host
         hostPoolSearch.and("hostId", hostPoolSearch.entity().getHostId(), Op.EQ);
@@ -470,17 +454,17 @@ protected String getSqlValuesFromStorageTags(String[] tags) throws NullPointerEx
         hostSearch.and("status", hostSearch.entity().getStatus(), Op.EQ);
         hostSearch.join("hostJoin", hostPoolSearch, hostSearch.entity().getId(), hostPoolSearch.entity().getPoolId(), JoinBuilder.JoinType.INNER);
 
-        if (!(tags == null || tags.length == 0 )) {
+        if (!(tags == null || tags.length == 0)) {
             tagPoolSearch.and("tag", tagPoolSearch.entity().getTag(), Op.EQ);
             hostSearch.join("tagJoin", tagPoolSearch, hostSearch.entity().getId(), tagPoolSearch.entity().getPoolId(), JoinBuilder.JoinType.INNER);
         }
 
         SearchCriteria<StoragePoolVO> sc = hostSearch.create();
-        sc.setJoinParameters("hostJoin", "hostId", hostId );
+        sc.setJoinParameters("hostJoin", "hostId", hostId);
         sc.setParameters("scope", ScopeType.HOST.toString());
         sc.setParameters("status", Status.Up.toString());
 
-        if (!(tags == null || tags.length == 0 )) {
+        if (!(tags == null || tags.length == 0)) {
             for (String tag : tags) {
                 sc.setJoinParameters("tagJoin", "tag", tag);
             }
@@ -516,7 +500,7 @@ public void updateDetails(long poolId, Map<String, String> details) {
                 detailsVO.add(new StoragePoolDetailVO(poolId, key, details.get(key), true));
             }
             _detailsDao.saveDetails(detailsVO);
-            if(details.size() == 0) {
+            if (details.size() == 0) {
                 _detailsDao.removeDetails(poolId);
             }
         }
@@ -571,4 +555,24 @@ public void deletePoolTags(long poolId) {
         _tagsDao.deleteTags(poolId);
     }
 
+    private String sqlIsSnapshotStoragePoolManaged = "select pool.id from snapshots s "
+            + " join volumes v on v.id = s.volume_id "
+            + " join storage_pool pool on pool.id = v.pool_id  "
+            + " where s.id = ?";
+
+    @Override
+    public StoragePoolVO findStoragePoolForSnapshot(long snapshotId) {
+        try (TransactionLegacy tx = TransactionLegacy.currentTxn();
+                PreparedStatement pstmt = tx.prepareAutoCloseStatement(sqlIsSnapshotStoragePoolManaged);) {
+            pstmt.setLong(1, snapshotId);
+            ResultSet rs = pstmt.executeQuery();
+            if (!rs.next()) {
+                throw new CloudRuntimeException(String.format("Could not find a storage pool for snapshot [snapshotId=%d]. ", snapshotId));
+            }
+            long storagePoolId = rs.getLong("id");
+            return findById(storagePoolId);
+        } catch (SQLException e) {
+            throw new CloudRuntimeException(e);
+        }
+    }
 }
diff --git a/engine/schema/test/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java b/engine/schema/test/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java
index 5b3d5d3c11a..2d195af0843 100755
--- a/engine/schema/test/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java
+++ b/engine/schema/test/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java
@@ -63,7 +63,7 @@
 
     private static final String DETAIL_KEY = "storage.overprovisioning.factor";
     private static final String DETAIL_VALUE = "2.0";
-    private static final Map<String, String> STORAGE_POOL_DETAILS = new HashMap<String, String>(){{ put(DETAIL_KEY, DETAIL_VALUE); }};
+    private static final Map<String, String> STORAGE_POOL_DETAILS = new HashMap<>();
 
     private static final String EXPECTED_RESULT_SQL_STORAGE_TAGS = "(storage_pool_tags.tag='" + STORAGE_TAG_1 + "') OR (storage_pool_tags.tag='" + STORAGE_TAG_2 + "')";
     private static final String EXPECTED_RESULT_SQL_DETAILS = "((storage_pool_details.name='" + DETAIL_KEY + "') AND (storage_pool_details.value='" + DETAIL_VALUE +"'))";
@@ -79,9 +79,10 @@
 
     @Before
     public void setup() {
+        STORAGE_POOL_DETAILS.put(DETAIL_KEY, DETAIL_VALUE);
         doReturn(Arrays.asList(storagePoolVO)).when(primaryDataStoreDao).
-            searchStoragePoolsPreparedStatement(Matchers.anyString(), Matchers.anyLong(), Matchers.anyLong(), Matchers.anyLong(),
-                    Matchers.any(ScopeType.class), Matchers.anyInt());
+        searchStoragePoolsPreparedStatement(Matchers.anyString(), Matchers.anyLong(), Matchers.anyLong(), Matchers.anyLong(),
+                Matchers.any(ScopeType.class), Matchers.anyInt());
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/XenServerGuru.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/XenServerGuru.java
index 74f989bf0f2..7d5279cd7cc 100644
--- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/XenServerGuru.java
+++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/XenServerGuru.java
@@ -32,6 +32,7 @@
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Command;
@@ -58,23 +59,25 @@
 import com.cloud.vm.dao.UserVmDao;
 
 public class XenServerGuru extends HypervisorGuruBase implements HypervisorGuru, Configurable {
-    private final Logger LOGGER = Logger.getLogger(XenServerGuru.class);
+
+    private Logger logger = Logger.getLogger(getClass());
+
     @Inject
-    private GuestOSDao _guestOsDao;
+    private GuestOSDao guestOsDao;
     @Inject
-    private GuestOSHypervisorDao _guestOsHypervisorDao;
+    private GuestOSHypervisorDao guestOsHypervisorDao;
     @Inject
     private HostDao hostDao;
     @Inject
-    private VolumeDao _volumeDao;
+    private VolumeDao volumeDao;
     @Inject
-    private PrimaryDataStoreDao _storagePoolDao;
+    private PrimaryDataStoreDao storagePoolDao;
     @Inject
-    private VolumeDataFactory _volFactory;
+    private VolumeDataFactory volFactory;
     @Inject
-    private UserVmDao _userVmDao;
+    private UserVmDao userVmDao;
     @Inject
-    GuestOsDetailsDao _guestOsDetailsDao;
+    private GuestOsDetailsDao guestOsDetailsDao;
 
     private static final ConfigKey<Integer> MaxNumberOfVCPUSPerVM = new ConfigKey<Integer>("Advanced", Integer.class, "xen.vm.vcpu.max", "16",
             "Maximum number of VCPUs that VM can get in XenServer.", true, ConfigKey.Scope.Cluster);
@@ -91,7 +94,7 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) {
             bt = vm.getBootLoaderType();
         }
         VirtualMachineTO to = toVirtualMachineTO(vm);
-        UserVmVO userVmVO = _userVmDao.findById(vm.getId());
+        UserVmVO userVmVO = userVmDao.findById(vm.getId());
         if (userVmVO != null) {
             HostVO host = hostDao.findById(userVmVO.getHostId());
             if (host != null) {
@@ -102,9 +105,9 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) {
         to.setBootloader(bt);
 
         // Determine the VM's OS description
-        GuestOSVO guestOS = _guestOsDao.findByIdIncludingRemoved(vm.getVirtualMachine().getGuestOSId());
+        GuestOSVO guestOS = guestOsDao.findByIdIncludingRemoved(vm.getVirtualMachine().getGuestOSId());
 
-        Map<String, String> guestOsDetails = _guestOsDetailsDao.listDetailsKeyPairs(vm.getVirtualMachine().getGuestOSId());
+        Map<String, String> guestOsDetails = guestOsDetailsDao.listDetailsKeyPairs(vm.getVirtualMachine().getGuestOSId());
 
         to.setGuestOsDetails(guestOsDetails);
 
@@ -112,7 +115,7 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) {
         HostVO host = hostDao.findById(vm.getVirtualMachine().getHostId());
         GuestOSHypervisorVO guestOsMapping = null;
         if (host != null) {
-            guestOsMapping = _guestOsHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), getHypervisorType().toString(), host.getHypervisorVersion());
+            guestOsMapping = guestOsHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), getHypervisorType().toString(), host.getHypervisorVersion());
         }
         if (guestOsMapping == null || host == null) {
             to.setPlatformEmulator(null);
@@ -132,20 +135,20 @@ public boolean trackVmHostChange() {
     public List<Command> finalizeExpungeVolumes(VirtualMachine vm) {
         List<Command> commands = new ArrayList<Command>();
 
-        List<VolumeVO> volumes = _volumeDao.findByInstance(vm.getId());
+        List<VolumeVO> volumes = volumeDao.findByInstance(vm.getId());
 
         // it's OK in this case to send a detach command to the host for a root volume as this
         // will simply lead to the SR that supports the root volume being removed
         if (volumes != null) {
             for (VolumeVO volume : volumes) {
-                StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
+                StoragePoolVO storagePool = storagePoolDao.findById(volume.getPoolId());
 
                 // storagePool should be null if we are expunging a volume that was never
                 // attached to a VM that was started (the "trick" for storagePool to be null
                 // is that none of the VMs this volume may have been attached to were ever started,
                 // so the volume was never assigned to a storage pool)
                 if (storagePool != null && storagePool.isManaged()) {
-                    DataTO volTO = _volFactory.getVolume(volume.getId()).getTO();
+                    DataTO volTO = volFactory.getVolume(volume.getId()).getTO();
                     DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getPath(), volume.getVolumeType());
 
                     DettachCommand cmd = new DettachCommand(disk, vm.getInstanceName());
@@ -167,35 +170,62 @@ public boolean trackVmHostChange() {
 
     @Override
     public Pair<Boolean, Long> getCommandHostDelegation(long hostId, Command cmd) {
-        LOGGER.debug("getCommandHostDelegation: " + cmd.getClass());
         if (cmd instanceof StorageSubSystemCommand) {
             StorageSubSystemCommand c = (StorageSubSystemCommand)cmd;
             c.setExecuteInSequence(true);
         }
-        if (cmd instanceof CopyCommand) {
-            CopyCommand cpyCommand = (CopyCommand)cmd;
-            DataTO srcData = cpyCommand.getSrcTO();
-            DataTO destData = cpyCommand.getDestTO();
-
-            if (srcData.getHypervisorType() == HypervisorType.XenServer && srcData.getObjectType() == DataObjectType.SNAPSHOT &&
-                    destData.getObjectType() == DataObjectType.TEMPLATE) {
-                DataStoreTO srcStore = srcData.getDataStore();
-                DataStoreTO destStore = destData.getDataStore();
-                if (srcStore instanceof NfsTO && destStore instanceof NfsTO) {
-                    HostVO host = hostDao.findById(hostId);
-                    hostDao.loadDetails(host);
-                    String hypervisorVersion = host.getHypervisorVersion();
-                    String snapshotHotFixVersion = host.getDetail(XenserverConfigs.XS620HotFix);
-                    if (hypervisorVersion != null && !hypervisorVersion.equalsIgnoreCase("6.1.0")) {
-                        if (!(hypervisorVersion.equalsIgnoreCase("6.2.0") &&
-                                !(snapshotHotFixVersion != null && snapshotHotFixVersion.equalsIgnoreCase(XenserverConfigs.XSHotFix62ESP1004)))) {
-                            return new Pair<Boolean, Long>(Boolean.TRUE, new Long(host.getId()));
-                        }
-                    }
-                }
-            }
+        boolean isCopyCommand = cmd instanceof CopyCommand;
+        Pair<Boolean, Long> defaultHostToExecuteCommands = super.getCommandHostDelegation(hostId, cmd);
+        if (!isCopyCommand) {
+            logger.debug("We are returning the default host to execute commands because the command is not of Copy type.");
+            return defaultHostToExecuteCommands;
+        }
+        CopyCommand copyCommand = (CopyCommand)cmd;
+        DataTO srcData = copyCommand.getSrcTO();
+        DataTO destData = copyCommand.getDestTO();
+
+        boolean isSourceDataHypervisorXenServer = srcData.getHypervisorType() == HypervisorType.XenServer;
+        if (!isSourceDataHypervisorXenServer) {
+            logger.debug("We are returning the default host to execute commands because the target hypervisor of the source data is not XenServer.");
+            return defaultHostToExecuteCommands;
+        }
+        DataStoreTO srcStore = srcData.getDataStore();
+        DataStoreTO destStore = destData.getDataStore();
+        boolean isSourceAndDestinationNfsObjects = srcStore instanceof NfsTO && destStore instanceof NfsTO;
+        if (!isSourceAndDestinationNfsObjects) {
+            logger.debug("We are returning the default host to execute commands because the source and destination objects are not NFS type.");
+            return defaultHostToExecuteCommands;
+        }
+        boolean isSourceObjectSnapshotTypeAndDestinationObjectTemplateType = srcData.getObjectType() == DataObjectType.SNAPSHOT
+                && destData.getObjectType() == DataObjectType.TEMPLATE;
+        if (!isSourceObjectSnapshotTypeAndDestinationObjectTemplateType) {
+            logger.debug("We are returning the default host to execute commands because the source and destination objects are not snapshot and template respectively.");
+            return defaultHostToExecuteCommands;
+        }
+        long snapshotId = srcData.getId();
+        StoragePoolVO storagePoolVO = storagePoolDao.findStoragePoolForSnapshot(snapshotId);
+        HostVO hostCandidateToExecutedCommand = hostDao.findHostToOperateOnSnapshotBasedOnStoragePool(storagePoolVO);
+        hostDao.loadDetails(hostCandidateToExecutedCommand);
+        String hypervisorVersion = hostCandidateToExecutedCommand.getHypervisorVersion();
+        if (StringUtils.isBlank(hypervisorVersion)) {
+            logger.debug("We are returning the default host to execute commands because the hypervisor version is blank.");
+            return defaultHostToExecuteCommands;
+        }
+        boolean isXenServer610 = StringUtils.equals(hypervisorVersion, "6.1.0");
+        if (isXenServer610) {
+            logger.debug("We are returning the default host to execute commands because the hypervisor version is 6.1.0.");
+            return defaultHostToExecuteCommands;
+        }
+        String snapshotHotFixVersion = hostCandidateToExecutedCommand.getDetail(XenserverConfigs.XS620HotFix);
+        boolean isXenServer620 = StringUtils.equals(hypervisorVersion, "6.2.0");
+        if (isXenServer620 && !StringUtils.equalsIgnoreCase(XenserverConfigs.XSHotFix62ESP1004, snapshotHotFixVersion)) {
+            logger.debug(String.format(
+                    "We are returning the default host to execute commands because the hypervisor version is not 6.2.0 with hotfix ESP1004 [hypervisorVersion=%s, hotfixVersion=%s]",
+                    hypervisorVersion, snapshotHotFixVersion));
+            return defaultHostToExecuteCommands;
         }
-        return new Pair<Boolean, Long>(Boolean.FALSE, new Long(hostId));
+        logger.debug(String.format("We are changing the hostId to executed command from %d to %d.", hostId, hostCandidateToExecutedCommand.getId()));
+        return new Pair<Boolean, Long>(Boolean.TRUE, new Long(hostCandidateToExecutedCommand.getId()));
     }
 
     @Override
diff --git a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/XenServerGuruTest.java b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/XenServerGuruTest.java
new file mode 100644
index 00000000000..d67a4f18f0b
--- /dev/null
+++ b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/XenServerGuruTest.java
@@ -0,0 +1,235 @@
+// 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;
+
+import org.apache.cloudstack.hypervisor.xenserver.XenserverConfigs;
+import org.apache.cloudstack.storage.command.CopyCommand;
+import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.agent.api.Command;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.NfsTO;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.utils.Pair;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XenServerGuruTest {
+
+    @InjectMocks
+    private XenServerGuru xenServerGuru = new XenServerGuru();
+
+    @Mock
+    private HostDao hostDaoMock;
+
+    @Mock
+    private PrimaryDataStoreDao storagePoolDao;
+
+    private Long defaultHostId = 1l;
+
+    @Mock
+    private CopyCommand copyCommandMock;
+
+    @Mock
+    private DataTO sourceDataMock;
+
+    @Mock
+    private DataTO destinationDataMock;
+
+    @Mock
+    private HostVO hostMock;
+
+    @Mock
+    private StoragePoolVO storagePoolMock;
+
+    private Long changedHostId = 12l;
+
+    private long snapshotId = 5l;
+
+    @Before
+    public void beforeTest() {
+        Mockito.when(copyCommandMock.getSrcTO()).thenReturn(sourceDataMock);
+        Mockito.when(copyCommandMock.getDestTO()).thenReturn(destinationDataMock);
+        Mockito.when(hostMock.getId()).thenReturn(changedHostId);
+        Mockito.when(sourceDataMock.getId()).thenReturn(snapshotId);
+        Mockito.when(storagePoolDao.findStoragePoolForSnapshot(snapshotId)).thenReturn(storagePoolMock);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandNotCopyCommand() {
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, Mockito.mock(Command.class));
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    private void assertPairOfHostToExecuteCommandIsTheDefaultHostId(Pair<Boolean, Long> pairHostToExecuteCommand) {
+        Assert.assertFalse(pairHostToExecuteCommand.first());
+        Assert.assertEquals(defaultHostId, pairHostToExecuteCommand.second());
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommanIsStorageSubSystemCommand() {
+        StorageSubSystemCommand storageSubSystemCommandMock = Mockito.mock(StorageSubSystemCommand.class);
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, storageSubSystemCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+
+        Mockito.verify(storageSubSystemCommandMock).setExecuteInSequence(true);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandButSourceDataHypervisorIsNotXenServer() {
+        Mockito.when(sourceDataMock.getHypervisorType()).thenReturn(HypervisorType.Any);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerButSourceAndDestinationAreNotNfsObjects() {
+        Mockito.when(sourceDataMock.getDataStore()).thenReturn(Mockito.mock(DataStoreTO.class));
+        Mockito.when(destinationDataMock.getDataStore()).thenReturn(Mockito.mock(DataStoreTO.class));
+
+        Mockito.when(sourceDataMock.getHypervisorType()).thenReturn(HypervisorType.XenServer);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsButSourceIsNotSnapshotType() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+
+        Mockito.when(sourceDataMock.getHypervisorType()).thenReturn(HypervisorType.XenServer);
+        Mockito.when(sourceDataMock.getObjectType()).thenReturn(DataObjectType.VOLUME);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    private void configureSourceAndDestinationDataMockDataStoreAsNfsToType() {
+        Mockito.when(sourceDataMock.getDataStore()).thenReturn(Mockito.mock(NfsTO.class));
+        Mockito.when(destinationDataMock.getDataStore()).thenReturn(Mockito.mock(NfsTO.class));
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsAndSourceIsSnapshotTypeButDestinationIsNotTemplateType() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+
+        Mockito.when(sourceDataMock.getHypervisorType()).thenReturn(HypervisorType.XenServer);
+        Mockito.when(sourceDataMock.getObjectType()).thenReturn(DataObjectType.SNAPSHOT);
+        Mockito.when(destinationDataMock.getObjectType()).thenReturn(DataObjectType.VOLUME);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsAndSourceIsSnapshotAndDestinationIsTemplateButHypervisorVersionIsBlank() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+        configureSourceHypervisorAsXenServerAndSourceTypeAsSnapshotAndDestinationTypeAsTemplate();
+
+        Mockito.when(hostMock.getHypervisorVersion()).thenReturn(StringUtils.EMPTY);
+        Mockito.when(hostDaoMock.findHostToOperateOnSnapshotBasedOnStoragePool(storagePoolMock)).thenReturn(hostMock);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    private void configureSourceHypervisorAsXenServerAndSourceTypeAsSnapshotAndDestinationTypeAsTemplate() {
+        Mockito.when(sourceDataMock.getHypervisorType()).thenReturn(HypervisorType.XenServer);
+        Mockito.when(sourceDataMock.getObjectType()).thenReturn(DataObjectType.SNAPSHOT);
+        Mockito.when(destinationDataMock.getObjectType()).thenReturn(DataObjectType.TEMPLATE);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsAndSourceIsSnapshotAndDestinationIsTemplateButHypervisorVersionIsXenServer610() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+        configureSourceHypervisorAsXenServerAndSourceTypeAsSnapshotAndDestinationTypeAsTemplate();
+
+        Mockito.when(hostMock.getHypervisorVersion()).thenReturn("6.1.0");
+        Mockito.when(hostDaoMock.findHostToOperateOnSnapshotBasedOnStoragePool(storagePoolMock)).thenReturn(hostMock);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsAndSourceIsSnapshotAndDestinationIsTemplateAndHypervisorVersionIsXenServer620WithoutHotfixOfSnapshots() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+        configureSourceHypervisorAsXenServerAndSourceTypeAsSnapshotAndDestinationTypeAsTemplate();
+
+        Mockito.when(hostMock.getHypervisorVersion()).thenReturn("6.2.0");
+        Mockito.when(hostDaoMock.findHostToOperateOnSnapshotBasedOnStoragePool(storagePoolMock)).thenReturn(hostMock);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        assertPairOfHostToExecuteCommandIsTheDefaultHostId(pairHostToExecuteCommand);
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsAndSourceIsSnapshotAndDestinationIsTemplateAndHypervisorVersionIsXenServer620WithHotfixOfSnapshots() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+        configureSourceHypervisorAsXenServerAndSourceTypeAsSnapshotAndDestinationTypeAsTemplate();
+
+        Mockito.when(hostMock.getHypervisorVersion()).thenReturn("6.2.0");
+        Mockito.when(hostMock.getDetail(XenserverConfigs.XS620HotFix)).thenReturn(XenserverConfigs.XSHotFix62ESP1004);
+
+        Mockito.when(hostDaoMock.findHostToOperateOnSnapshotBasedOnStoragePool(storagePoolMock)).thenReturn(hostMock);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        Assert.assertTrue(pairHostToExecuteCommand.first());
+        Assert.assertEquals(changedHostId, pairHostToExecuteCommand.second());
+    }
+
+    @Test
+    public void getCommandHostDelegationTestCommandIsCopyCommandAndSourceDataHypervisorIsXenServerAndSourceAndDestinationAreNfsObjectsAndSourceIsSnapshotAndDestinationIsTemplateAndHypervisorVersionIsXenServer650() {
+        configureSourceAndDestinationDataMockDataStoreAsNfsToType();
+        configureSourceHypervisorAsXenServerAndSourceTypeAsSnapshotAndDestinationTypeAsTemplate();
+
+        Mockito.when(hostMock.getHypervisorVersion()).thenReturn("6.5.0");
+
+        Mockito.when(hostDaoMock.findHostToOperateOnSnapshotBasedOnStoragePool(storagePoolMock)).thenReturn(hostMock);
+
+        Pair<Boolean, Long> pairHostToExecuteCommand = xenServerGuru.getCommandHostDelegation(defaultHostId, copyCommandMock);
+
+        Assert.assertTrue(pairHostToExecuteCommand.first());
+        Assert.assertEquals(changedHostId, pairHostToExecuteCommand.second());
+    }
+}
diff --git a/pom.xml b/pom.xml
index bc76817c0d8..0a0650dc7d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
 <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/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
-
+ 
   <parent>
     <groupId>org.apache</groupId>
     <artifactId>apache</artifactId>
diff --git a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java
index 0bf1c705759..9f67d34b523 100644
--- a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java
+++ b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java
@@ -131,16 +131,15 @@ protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) {
         Long minMemory = (long)(offering.getRamSize() / vmProfile.getMemoryOvercommitRatio());
         int minspeed = (int)(offering.getSpeed() / vmProfile.getCpuOvercommitRatio());
         int maxspeed = (offering.getSpeed());
-        VirtualMachineTO to =
-                new VirtualMachineTO(vm.getId(), vm.getInstanceName(), vm.getType(), offering.getCpu(), minspeed, maxspeed, minMemory * 1024l * 1024l,
-                        offering.getRamSize() * 1024l * 1024l, null, null, vm.isHaEnabled(), vm.limitCpuUse(), vm.getVncPassword());
+        VirtualMachineTO to = new VirtualMachineTO(vm.getId(), vm.getInstanceName(), vm.getType(), offering.getCpu(), minspeed, maxspeed, minMemory * 1024l * 1024l,
+                offering.getRamSize() * 1024l * 1024l, null, null, vm.isHaEnabled(), vm.limitCpuUse(), vm.getVncPassword());
         to.setBootArgs(vmProfile.getBootArgs());
 
         List<NicProfile> nicProfiles = vmProfile.getNics();
         NicTO[] nics = new NicTO[nicProfiles.size()];
         int i = 0;
         for (NicProfile nicProfile : nicProfiles) {
-            if(vm.getType() == VirtualMachine.Type.NetScalerVm) {
+            if (vm.getType() == VirtualMachine.Type.NetScalerVm) {
                 nicProfile.setBroadcastType(BroadcastDomainType.Native);
             }
             NicTO nicTo = toNicTO(nicProfile);
@@ -173,7 +172,7 @@ protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) {
 
         // Set GPU details
         ServiceOfferingDetailsVO offeringDetail = null;
-        if ((offeringDetail  = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString())) != null) {
+        if ((offeringDetail = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString())) != null) {
             ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString());
             to.setGpuDevice(_resourceMgr.getGPUDevice(vm.getHostId(), groupName.getValue(), offeringDetail.getValue()));
         }
@@ -194,6 +193,13 @@ protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) {
     }
 
     @Override
+    /**
+     * The basic implementation assumes that the initial "host" defined to execute the command is the host that is in fact going to execute it.
+     * However, subclasses can extend this behavior, changing the host that is going to execute the command in runtime.
+     * The first element of the 'Pair' indicates if the hostId has been changed; this means, if you change the hostId, but you do not inform this action in the return 'Pair' object, we will use the original "hostId".
+     *
+     * Side note: it seems that the 'hostId' received here is normally the ID of the SSVM that has an entry at the host table. Therefore, this methods gives the opportunity to change from the SSVM to a real host to execute a command.
+     */
     public Pair<Boolean, Long> getCommandHostDelegation(long hostId, Command cmd) {
         return new Pair<Boolean, Long>(Boolean.FALSE, new Long(hostId));
     }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services