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

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

commit 46c56eaaf9834680054c2f10d79012476c59a30e
Merge: 96d80b1 e4ec123
Author: Mike Tutkowski <mike.tutkow...@solidfire.com>
AuthorDate: Sun Aug 12 00:03:37 2018 -0600

    Merge release branch 4.11 to master
    
    * 4.11:
      Changed the implementation of isVolumeOnManagedStorage(VolumeInfo) to 
check if the data store in question is for primary storage (and added a unit 
test from Daan Hoogland)
      vmware: reboot VR after mac updates (#2794)

 .../motion/StorageSystemDataMotionStrategy.java    | 12 +++-
 .../StorageSystemDataMotionStrategyTest.java       | 84 ++++++++++++++++++++++
 .../consoleproxy/ConsoleProxyManagerImpl.java      |  4 +-
 systemvm/debian/opt/cloud/bin/setup/router.sh      |  4 ++
 ui/scripts/instances.js                            |  3 +
 ui/scripts/system.js                               | 13 +++-
 6 files changed, 114 insertions(+), 6 deletions(-)

diff --cc 
engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
index 8efaebe,0000000..1957f82
mode 100644,000000..100644
--- 
a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
+++ 
b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
@@@ -1,2290 -1,0 +1,2296 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.cloudstack.storage.motion;
 +
 +import com.cloud.agent.AgentManager;
 +import com.cloud.agent.api.Answer;
 +import com.cloud.agent.api.storage.CopyVolumeAnswer;
 +import com.cloud.agent.api.storage.CopyVolumeCommand;
 +import com.cloud.agent.api.MigrateAnswer;
 +import com.cloud.agent.api.MigrateCommand;
 +import com.cloud.agent.api.ModifyTargetsAnswer;
 +import com.cloud.agent.api.ModifyTargetsCommand;
 +import com.cloud.agent.api.PrepareForMigrationCommand;
 +import com.cloud.agent.api.storage.MigrateVolumeAnswer;
 +import com.cloud.agent.api.storage.MigrateVolumeCommand;
 +import com.cloud.agent.api.to.DataStoreTO;
 +import com.cloud.agent.api.to.DataTO;
 +import com.cloud.agent.api.to.DiskTO;
 +import com.cloud.agent.api.to.NfsTO;
 +import com.cloud.agent.api.to.VirtualMachineTO;
 +import com.cloud.configuration.Config;
 +import com.cloud.dc.dao.ClusterDao;
 +import com.cloud.exception.AgentUnavailableException;
 +import com.cloud.exception.OperationTimedoutException;
 +import com.cloud.host.Host;
 +import com.cloud.host.HostVO;
 +import com.cloud.host.dao.HostDao;
 +import com.cloud.host.dao.HostDetailsDao;
 +import com.cloud.hypervisor.Hypervisor.HypervisorType;
 +import com.cloud.resource.ResourceState;
 +import com.cloud.storage.DataStoreRole;
 +import com.cloud.storage.DiskOfferingVO;
 +import com.cloud.storage.Snapshot;
 +import com.cloud.storage.SnapshotVO;
 +import com.cloud.storage.Storage.ImageFormat;
 +import com.cloud.storage.StorageManager;
 +import com.cloud.storage.VMTemplateVO;
 +import com.cloud.storage.VolumeDetailVO;
 +import com.cloud.storage.Volume;
 +import com.cloud.storage.VolumeVO;
 +import com.cloud.storage.dao.DiskOfferingDao;
 +import com.cloud.storage.dao.GuestOSCategoryDao;
 +import com.cloud.storage.dao.GuestOSDao;
 +import com.cloud.storage.dao.SnapshotDao;
 +import com.cloud.storage.dao.SnapshotDetailsDao;
 +import com.cloud.storage.dao.SnapshotDetailsVO;
 +import com.cloud.storage.dao.VMTemplateDao;
 +import com.cloud.storage.dao.VolumeDao;
 +import com.cloud.storage.dao.VolumeDetailsDao;
 +import com.cloud.utils.NumbersUtil;
 +import com.cloud.utils.db.GlobalLock;
 +import com.cloud.utils.exception.CloudRuntimeException;
 +import com.cloud.vm.VirtualMachine;
 +import com.cloud.vm.VirtualMachineManager;
 +import com.cloud.vm.VMInstanceVO;
 +import com.cloud.vm.dao.VMInstanceDao;
 +
 +import com.google.common.base.Preconditions;
 +
 +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
 +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 +import 
org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
 +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
 +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
 +import 
org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
 +import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
 +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
 +import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
 +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 +import 
org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult;
 +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
 +import org.apache.cloudstack.framework.async.AsyncCallFuture;
 +import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 +import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 +import org.apache.cloudstack.storage.command.CopyCmdAnswer;
 +import org.apache.cloudstack.storage.command.CopyCommand;
 +import org.apache.cloudstack.storage.command.ResignatureAnswer;
 +import org.apache.cloudstack.storage.command.ResignatureCommand;
 +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 +import org.apache.cloudstack.storage.to.VolumeObjectTO;
 +import org.apache.commons.lang.StringUtils;
 +import org.apache.commons.lang.math.NumberUtils;
 +import org.apache.log4j.Logger;
 +
 +import org.springframework.stereotype.Component;
 +
 +import javax.inject.Inject;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Random;
 +import java.util.Set;
 +import java.util.UUID;
 +import java.util.concurrent.TimeUnit;
 +
 +@Component
 +public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
 +    private static final Logger LOGGER = 
Logger.getLogger(StorageSystemDataMotionStrategy.class);
 +    private static final Random RANDOM = new Random(System.nanoTime());
 +    private static final int LOCK_TIME_IN_SECONDS = 300;
 +    private static final String OPERATION_NOT_SUPPORTED = "This operation is 
not supported.";
 +
 +    @Inject private AgentManager _agentMgr;
 +    @Inject private ConfigurationDao _configDao;
 +    @Inject private DataStoreManager dataStoreMgr;
 +    @Inject private DiskOfferingDao _diskOfferingDao;
 +    @Inject private GuestOSCategoryDao _guestOsCategoryDao;
 +    @Inject private GuestOSDao _guestOsDao;
 +    @Inject private ClusterDao clusterDao;
 +    @Inject private HostDao _hostDao;
 +    @Inject private HostDetailsDao hostDetailsDao;
 +    @Inject private PrimaryDataStoreDao _storagePoolDao;
 +    @Inject private SnapshotDao _snapshotDao;
 +    @Inject private SnapshotDataStoreDao _snapshotDataStoreDao;
 +    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
 +    @Inject private VMInstanceDao _vmDao;
 +    @Inject private VMTemplateDao _vmTemplateDao;
 +    @Inject private VolumeDao _volumeDao;
 +    @Inject private VolumeDataFactory _volumeDataFactory;
 +    @Inject private VolumeDetailsDao volumeDetailsDao;
 +    @Inject private VolumeService _volumeService;
 +    @Inject private StorageCacheManager cacheMgr;
 +    @Inject private EndPointSelector selector;
 +
 +    @Override
 +    public StrategyPriority canHandle(DataObject srcData, DataObject 
destData) {
 +        if (srcData instanceof SnapshotInfo) {
 +            if (canHandle(srcData) || canHandle(destData)) {
 +                return StrategyPriority.HIGHEST;
 +            }
 +        }
 +
 +        if (srcData instanceof TemplateInfo && destData instanceof VolumeInfo 
&&
 +                (srcData.getDataStore().getId() == 
destData.getDataStore().getId()) &&
 +                (canHandle(srcData) || canHandle(destData))) {
 +            // Both source and dest are on the same storage, so just clone 
them.
 +            return StrategyPriority.HIGHEST;
 +        }
 +
 +        if (srcData instanceof VolumeInfo && destData instanceof VolumeInfo) {
 +            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
 +
 +            if (isVolumeOnManagedStorage(srcVolumeInfo)) {
 +                return StrategyPriority.HIGHEST;
 +            }
 +
 +            VolumeInfo destVolumeInfo = (VolumeInfo)destData;
 +
 +            if (isVolumeOnManagedStorage(destVolumeInfo)) {
 +                return StrategyPriority.HIGHEST;
 +            }
 +        }
 +
 +        if (srcData instanceof VolumeInfo && destData instanceof 
TemplateInfo) {
 +            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
 +
 +            if (isVolumeOnManagedStorage(srcVolumeInfo)) {
 +                return StrategyPriority.HIGHEST;
 +            }
 +        }
 +
 +        return StrategyPriority.CANT_HANDLE;
 +    }
 +
 +    private boolean isVolumeOnManagedStorage(VolumeInfo volumeInfo) {
-         long storagePooldId = volumeInfo.getDataStore().getId();
-         StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(storagePooldId);
++        DataStore dataStore = volumeInfo.getDataStore();
 +
-         return storagePoolVO.isManaged();
++        if (dataStore.getRole() == DataStoreRole.Primary) {
++            long storagePooldId = dataStore.getId();
++            StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(storagePooldId);
++
++            return storagePoolVO.isManaged();
++        }
++
++        return false;
 +    }
 +
 +    // canHandle returns true if the storage driver for the DataObject that's 
passed in can support certain features (what features we
 +    // care about during a particular invocation of this method depend on 
what type of DataObject was passed in (ex. VolumeInfo versus SnapshotInfo)).
 +    private boolean canHandle(DataObject dataObject) {
 +        Preconditions.checkArgument(dataObject != null, "Passing 'null' to 
dataObject of canHandle(DataObject) is not supported.");
 +
 +        DataStore dataStore = dataObject.getDataStore();
 +
 +        if (dataStore.getRole() == DataStoreRole.Primary) {
 +            Map<String, String> mapCapabilities = 
dataStore.getDriver().getCapabilities();
 +
 +            if (mapCapabilities == null) {
 +                return false;
 +            }
 +
 +            if (dataObject instanceof VolumeInfo || dataObject instanceof 
SnapshotInfo) {
 +                String value = 
mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
 +                Boolean supportsStorageSystemSnapshots = 
Boolean.valueOf(value);
 +
 +                if (supportsStorageSystemSnapshots) {
 +                    LOGGER.info("Using 'StorageSystemDataMotionStrategy' 
(dataObject is a volume or snapshot and the storage system supports 
snapshots)");
 +
 +                    return true;
 +                }
 +            } else if (dataObject instanceof TemplateInfo) {
 +                // If the storage system can clone volumes, we can cache 
templates on it.
 +                String value = 
mapCapabilities.get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString());
 +                Boolean canCloneVolume = Boolean.valueOf(value);
 +
 +                if (canCloneVolume) {
 +                    LOGGER.info("Using 'StorageSystemDataMotionStrategy' 
(dataObject is a template and the storage system can create a volume from a 
volume)");
 +
 +                    return true;
 +                }
 +            }
 +        }
 +
 +        return false;
 +    }
 +
 +    @Override
 +    public StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, 
Host srcHost, Host destHost) {
 +        if (HypervisorType.KVM.equals(srcHost.getHypervisorType())) {
 +            Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
 +
 +            for (VolumeInfo volumeInfo : volumeInfoSet) {
 +                StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(volumeInfo.getPoolId());
 +
 +                if (storagePoolVO.isManaged()) {
 +                    return StrategyPriority.HIGHEST;
 +                }
 +            }
 +
 +            Collection<DataStore> dataStores = volumeMap.values();
 +
 +            for (DataStore dataStore : dataStores) {
 +                StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(dataStore.getId());
 +
 +                if (storagePoolVO.isManaged()) {
 +                    return StrategyPriority.HIGHEST;
 +                }
 +            }
 +        }
 +
 +        return StrategyPriority.CANT_HANDLE;
 +    }
 +
 +    @Override
 +    public void copyAsync(DataObject srcData, DataObject destData, Host 
destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
 +        if (srcData instanceof SnapshotInfo) {
 +            SnapshotInfo srcSnapshotInfo = (SnapshotInfo)srcData;
 +
 +            handleCopyAsyncForSnapshot(srcSnapshotInfo, destData, callback);
 +        } else if (srcData instanceof TemplateInfo && destData instanceof 
VolumeInfo) {
 +            TemplateInfo srcTemplateInfo = (TemplateInfo)srcData;
 +            VolumeInfo destVolumeInfo = (VolumeInfo)destData;
 +
 +            handleCopyAsyncForTemplateAndVolume(srcTemplateInfo, 
destVolumeInfo, callback);
 +        } else if (srcData instanceof VolumeInfo && destData instanceof 
VolumeInfo) {
 +            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
 +            VolumeInfo destVolumeInfo = (VolumeInfo)destData;
 +
 +            handleCopyAsyncForVolumes(srcVolumeInfo, destVolumeInfo, 
callback);
 +        } else if (srcData instanceof VolumeInfo && destData instanceof 
TemplateInfo &&
 +                (destData.getDataStore().getRole() == DataStoreRole.Image || 
destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
 +            VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
 +            TemplateInfo destTemplateInfo = (TemplateInfo)destData;
 +
 +            handleCreateTemplateFromVolume(srcVolumeInfo, destTemplateInfo, 
callback);
 +        }
 +        else {
 +            handleError(OPERATION_NOT_SUPPORTED, callback);
 +        }
 +    }
 +
 +    private void handleCopyAsyncForSnapshot(SnapshotInfo srcSnapshotInfo, 
DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
 +        verifyFormat(srcSnapshotInfo);
 +
 +        boolean canHandleSrc = canHandle(srcSnapshotInfo);
 +
 +        if (canHandleSrc && (destData instanceof TemplateInfo || destData 
instanceof SnapshotInfo) &&
 +                (destData.getDataStore().getRole() == DataStoreRole.Image || 
destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
 +            handleCopyDataToSecondaryStorage(srcSnapshotInfo, destData, 
callback);
 +        } else if (destData instanceof VolumeInfo) {
 +            handleCopyAsyncForSnapshotToVolume(srcSnapshotInfo, 
(VolumeInfo)destData, callback);
 +        } else {
 +            handleError(OPERATION_NOT_SUPPORTED, callback);
 +        }
 +    }
 +
 +    private void handleCopyAsyncForSnapshotToVolume(SnapshotInfo 
srcSnapshotInfo, VolumeInfo destVolumeInfo,
 +                                                    
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        boolean canHandleDest = canHandle(destVolumeInfo);
 +
 +        if (!canHandleDest) {
 +            handleError(OPERATION_NOT_SUPPORTED, callback);
 +        }
 +
 +        boolean canHandleSrc = canHandle(srcSnapshotInfo);
 +
 +        if (!canHandleSrc) {
 +            handleCreateVolumeFromSnapshotOnSecondaryStorage(srcSnapshotInfo, 
destVolumeInfo, callback);
 +        }
 +
 +        if (srcSnapshotInfo.getDataStore().getId() == 
destVolumeInfo.getDataStore().getId()) {
 +            
handleCreateVolumeFromSnapshotBothOnStorageSystem(srcSnapshotInfo, 
destVolumeInfo, callback);
 +        } else {
 +            String errMsg = "To perform this operation, the source and 
destination primary storages must be the same.";
 +
 +            handleError(errMsg, callback);
 +        }
 +    }
 +
 +    private void handleCopyAsyncForTemplateAndVolume(TemplateInfo 
srcTemplateInfo, VolumeInfo destVolumeInfo, 
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        boolean canHandleSrc = canHandle(srcTemplateInfo);
 +
 +        if (!canHandleSrc) {
 +            handleError(OPERATION_NOT_SUPPORTED, callback);
 +        }
 +
 +        handleCreateVolumeFromTemplateBothOnStorageSystem(srcTemplateInfo, 
destVolumeInfo, callback);
 +    }
 +
 +    private void handleCopyAsyncForVolumes(VolumeInfo srcVolumeInfo, 
VolumeInfo destVolumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) 
{
 +        if (srcVolumeInfo.getState() == Volume.State.Migrating) {
 +            if (isVolumeOnManagedStorage(srcVolumeInfo)) {
 +                if (destVolumeInfo.getDataStore().getRole() == 
DataStoreRole.Image || destVolumeInfo.getDataStore().getRole() == 
DataStoreRole.ImageCache) {
 +                    
handleVolumeCopyFromManagedStorageToSecondaryStorage(srcVolumeInfo, 
destVolumeInfo, callback);
 +                } else if (!isVolumeOnManagedStorage(destVolumeInfo)) {
 +                    
handleVolumeMigrationFromManagedStorageToNonManagedStorage(srcVolumeInfo, 
destVolumeInfo, callback);
 +                } else {
 +                    String errMsg = "The source volume to migrate and the 
destination volume are both on managed storage. " +
 +                            "Migration in this case is not yet supported.";
 +
 +                    handleError(errMsg, callback);
 +                }
 +            } else if (!isVolumeOnManagedStorage(destVolumeInfo)) {
 +                String errMsg = "The 'StorageSystemDataMotionStrategy' does 
not support this migration use case.";
 +
 +                handleError(errMsg, callback);
 +            } else {
 +                
handleVolumeMigrationFromNonManagedStorageToManagedStorage(srcVolumeInfo, 
destVolumeInfo, callback);
 +            }
 +        } else if (srcVolumeInfo.getState() == Volume.State.Uploaded &&
 +                (srcVolumeInfo.getDataStore().getRole() == 
DataStoreRole.Image || srcVolumeInfo.getDataStore().getRole() == 
DataStoreRole.ImageCache) &&
 +                destVolumeInfo.getDataStore().getRole() == 
DataStoreRole.Primary) {
 +            ImageFormat imageFormat = destVolumeInfo.getFormat();
 +
 +            if (!ImageFormat.QCOW2.equals(imageFormat)) {
 +                String errMsg = "The 'StorageSystemDataMotionStrategy' does 
not support this upload use case (non KVM).";
 +
 +                handleError(errMsg, callback);
 +            }
 +
 +            handleCreateVolumeFromVolumeOnSecondaryStorage(srcVolumeInfo, 
destVolumeInfo, destVolumeInfo.getDataCenterId(), HypervisorType.KVM, callback);
 +        } else {
 +            handleError(OPERATION_NOT_SUPPORTED, callback);
 +        }
 +    }
 +
 +    private void handleError(String errMsg, 
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        LOGGER.warn(errMsg);
 +
 +        invokeCallback(errMsg, callback);
 +
 +        throw new UnsupportedOperationException(errMsg);
 +    }
 +
 +    private void invokeCallback(String errMsg, 
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +
 +        CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
 +
 +        result.setResult(errMsg);
 +
 +        callback.complete(result);
 +    }
 +
 +    private void 
handleVolumeCopyFromManagedStorageToSecondaryStorage(VolumeInfo srcVolumeInfo, 
VolumeInfo destVolumeInfo,
 +                                                                      
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +        String volumePath = null;
 +
 +        try {
 +            if (!ImageFormat.QCOW2.equals(srcVolumeInfo.getFormat())) {
 +                throw new CloudRuntimeException("Currently, only the KVM 
hypervisor type is supported for the migration of a volume " +
 +                        "from managed storage to non-managed storage.");
 +            }
 +
 +            HypervisorType hypervisorType = HypervisorType.KVM;
 +            VirtualMachine vm = srcVolumeInfo.getAttachedVM();
 +
 +            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
 +                throw new CloudRuntimeException("Currently, if a volume to 
copy from managed storage to secondary storage is attached to " +
 +                        "a VM, the VM must be in the Stopped state.");
 +            }
 +
 +            long srcStoragePoolId = srcVolumeInfo.getPoolId();
 +            StoragePoolVO srcStoragePoolVO = 
_storagePoolDao.findById(srcStoragePoolId);
 +
 +            HostVO hostVO;
 +
 +            if (srcStoragePoolVO.getClusterId() != null) {
 +                hostVO = getHostInCluster(srcStoragePoolVO.getClusterId());
 +            }
 +            else {
 +                hostVO = getHost(srcVolumeInfo.getDataCenterId(), 
hypervisorType, false);
 +            }
 +
 +            volumePath = copyVolumeToSecondaryStorage(srcVolumeInfo, 
destVolumeInfo, hostVO,
 +                    "Unable to copy the volume from managed storage to 
secondary storage");
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Migration operation failed in 
'StorageSystemDataMotionStrategy.handleVolumeCopyFromManagedStorageToSecondaryStorage':
 " +
 +                    ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            CopyCmdAnswer copyCmdAnswer;
 +
 +            if (errMsg != null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +            else if (volumePath == null) {
 +                copyCmdAnswer = new CopyCmdAnswer("Unable to acquire a volume 
path");
 +            }
 +            else {
 +                VolumeObjectTO volumeObjectTO = 
(VolumeObjectTO)destVolumeInfo.getTO();
 +
 +                volumeObjectTO.setPath(volumePath);
 +
 +                copyCmdAnswer = new CopyCmdAnswer(volumeObjectTO);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private void 
handleVolumeMigrationFromManagedStorageToNonManagedStorage(VolumeInfo 
srcVolumeInfo, VolumeInfo destVolumeInfo,
 +                                                                            
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +
 +        try {
 +            if (!ImageFormat.QCOW2.equals(srcVolumeInfo.getFormat())) {
 +                throw new CloudRuntimeException("Currently, only the KVM 
hypervisor type is supported for the migration of a volume " +
 +                        "from managed storage to non-managed storage.");
 +            }
 +
 +            HypervisorType hypervisorType = HypervisorType.KVM;
 +            VirtualMachine vm = srcVolumeInfo.getAttachedVM();
 +
 +            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
 +                throw new CloudRuntimeException("Currently, if a volume to 
migrate from managed storage to non-managed storage is attached to " +
 +                        "a VM, the VM must be in the Stopped state.");
 +            }
 +
 +            long destStoragePoolId = destVolumeInfo.getPoolId();
 +            StoragePoolVO destStoragePoolVO = 
_storagePoolDao.findById(destStoragePoolId);
 +
 +            HostVO hostVO;
 +
 +            if (destStoragePoolVO.getClusterId() != null) {
 +                hostVO = getHostInCluster(destStoragePoolVO.getClusterId());
 +            }
 +            else {
 +                hostVO = getHost(destVolumeInfo.getDataCenterId(), 
hypervisorType, false);
 +            }
 +
 +            setCertainVolumeValuesNull(destVolumeInfo.getId());
 +
 +            // migrate the volume via the hypervisor
 +            String path = migrateVolume(srcVolumeInfo, destVolumeInfo, 
hostVO, "Unable to migrate the volume from managed storage to non-managed 
storage");
 +
 +            updateVolumePath(destVolumeInfo.getId(), path);
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Migration operation failed in 
'StorageSystemDataMotionStrategy.handleVolumeMigrationFromManagedStorageToNonManagedStorage':
 " +
 +                    ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            CopyCmdAnswer copyCmdAnswer;
 +
 +            if (errMsg != null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +            else {
 +                destVolumeInfo = 
_volumeDataFactory.getVolume(destVolumeInfo.getId(), 
destVolumeInfo.getDataStore());
 +
 +                DataTO dataTO = destVolumeInfo.getTO();
 +
 +                copyCmdAnswer = new CopyCmdAnswer(dataTO);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private void verifyFormat(ImageFormat imageFormat) {
 +        if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA 
&& imageFormat != ImageFormat.QCOW2) {
 +            throw new CloudRuntimeException("Only the following image types 
are currently supported: " +
 +                    ImageFormat.VHD.toString() + ", " + 
ImageFormat.OVA.toString() + ", and " + ImageFormat.QCOW2);
 +        }
 +    }
 +
 +    private void verifyFormat(SnapshotInfo snapshotInfo) {
 +        long volumeId = snapshotInfo.getVolumeId();
 +
 +        VolumeVO volumeVO = _volumeDao.findByIdIncludingRemoved(volumeId);
 +
 +        verifyFormat(volumeVO.getFormat());
 +    }
 +
 +    private boolean usingBackendSnapshotFor(SnapshotInfo snapshotInfo) {
 +        String property = getSnapshotProperty(snapshotInfo.getId(), 
"takeSnapshot");
 +
 +        return Boolean.parseBoolean(property);
 +    }
 +
 +    private boolean needCacheStorage(DataObject srcData, DataObject destData) 
{
 +        DataTO srcTO = srcData.getTO();
 +        DataStoreTO srcStoreTO = srcTO.getDataStore();
 +        DataTO destTO = destData.getTO();
 +        DataStoreTO destStoreTO = destTO.getDataStore();
 +
 +        // both snapshot and volume are on primary datastore - no need for a 
cache storage as hypervisor will copy directly
 +        if (srcStoreTO instanceof PrimaryDataStoreTO && destStoreTO 
instanceof PrimaryDataStoreTO) {
 +            return false;
 +        }
 +
 +        if (srcStoreTO instanceof NfsTO || srcStoreTO.getRole() == 
DataStoreRole.ImageCache) {
 +            return false;
 +        }
 +
 +        if (destStoreTO instanceof NfsTO || destStoreTO.getRole() == 
DataStoreRole.ImageCache) {
 +            return false;
 +        }
 +
 +        if (LOGGER.isDebugEnabled()) {
 +            LOGGER.debug("needCacheStorage true; dest at " + destTO.getPath() 
+ ", dest role " + destStoreTO.getRole().toString() + "; src at " +
 +                    srcTO.getPath() + ", src role " + 
srcStoreTO.getRole().toString());
 +        }
 +
 +        return true;
 +    }
 +
 +    private Scope pickCacheScopeForCopy(DataObject srcData, DataObject 
destData) {
 +        Scope srcScope = srcData.getDataStore().getScope();
 +        Scope destScope = destData.getDataStore().getScope();
 +
 +        Scope selectedScope = null;
 +
 +        if (srcScope.getScopeId() != null) {
 +            selectedScope = getZoneScope(srcScope);
 +        } else if (destScope.getScopeId() != null) {
 +            selectedScope = getZoneScope(destScope);
 +        } else {
 +            LOGGER.warn("Cannot find a zone-wide scope for movement that 
needs a cache storage");
 +        }
 +
 +        return selectedScope;
 +    }
 +
 +    private Scope getZoneScope(Scope scope) {
 +        ZoneScope zoneScope;
 +
 +        if (scope instanceof ClusterScope) {
 +            ClusterScope clusterScope = (ClusterScope)scope;
 +
 +            zoneScope = new ZoneScope(clusterScope.getZoneId());
 +        } else if (scope instanceof HostScope) {
 +            HostScope hostScope = (HostScope)scope;
 +
 +            zoneScope = new ZoneScope(hostScope.getZoneId());
 +        } else {
 +            zoneScope = (ZoneScope)scope;
 +        }
 +
 +        return zoneScope;
 +    }
 +
 +    private void 
handleVolumeMigrationFromNonManagedStorageToManagedStorage(VolumeInfo 
srcVolumeInfo, VolumeInfo destVolumeInfo,
 +                                                                            
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +
 +        try {
 +            HypervisorType hypervisorType = srcVolumeInfo.getHypervisorType();
 +
 +            if (!HypervisorType.KVM.equals(hypervisorType)) {
 +                throw new CloudRuntimeException("Currently, only the KVM 
hypervisor type is supported for the migration of a volume " +
 +                        "from non-managed storage to managed storage.");
 +            }
 +
 +            VirtualMachine vm = srcVolumeInfo.getAttachedVM();
 +
 +            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
 +                throw new CloudRuntimeException("Currently, if a volume to 
migrate from non-managed storage to managed storage is attached to " +
 +                        "a VM, the VM must be in the Stopped state.");
 +            }
 +
 +            
destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(),
 destVolumeInfo, null);
 +
 +            VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
 +
 +            volumeVO.setPath(volumeVO.get_iScsiName());
 +
 +            _volumeDao.update(volumeVO.getId(), volumeVO);
 +
 +            destVolumeInfo = 
_volumeDataFactory.getVolume(destVolumeInfo.getId(), 
destVolumeInfo.getDataStore());
 +
 +            long srcStoragePoolId = srcVolumeInfo.getPoolId();
 +            StoragePoolVO srcStoragePoolVO = 
_storagePoolDao.findById(srcStoragePoolId);
 +
 +            HostVO hostVO;
 +
 +            if (srcStoragePoolVO.getClusterId() != null) {
 +                hostVO = getHostInCluster(srcStoragePoolVO.getClusterId());
 +            }
 +            else {
 +                hostVO = getHost(destVolumeInfo.getDataCenterId(), 
hypervisorType, false);
 +            }
 +
 +            // migrate the volume via the hypervisor
 +            migrateVolume(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to 
migrate the volume from non-managed storage to managed storage");
 +
 +            volumeVO = _volumeDao.findById(destVolumeInfo.getId());
 +
 +            volumeVO.setFormat(ImageFormat.QCOW2);
 +
 +            _volumeDao.update(volumeVO.getId(), volumeVO);
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Migration operation failed in 
'StorageSystemDataMotionStrategy.handleVolumeMigrationFromNonManagedStorageToManagedStorage':
 " +
 +                    ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            CopyCmdAnswer copyCmdAnswer;
 +
 +            if (errMsg != null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +            else {
 +                destVolumeInfo = 
_volumeDataFactory.getVolume(destVolumeInfo.getId(), 
destVolumeInfo.getDataStore());
 +
 +                DataTO dataTO = destVolumeInfo.getTO();
 +
 +                copyCmdAnswer = new CopyCmdAnswer(dataTO);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    /**
 +     * This function is responsible for copying a snapshot from managed 
storage to secondary storage. This is used in the following two cases:
 +     * 1) When creating a template from a snapshot
 +     * 2) When createSnapshot is called with location=SECONDARY
 +     *
 +     * @param snapshotInfo source snapshot
 +     * @param destData destination (can be template or snapshot)
 +     * @param callback callback for async
 +     */
 +    private void handleCopyDataToSecondaryStorage(SnapshotInfo snapshotInfo, 
DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +        boolean usingBackendSnapshot = false;
 +
 +        try {
 +            snapshotInfo.processEvent(Event.CopyingRequested);
 +
 +            HostVO hostVO = getHost(snapshotInfo);
 +
 +            boolean needCache = needCacheStorage(snapshotInfo, destData);
 +
 +            DataObject destOnStore = destData;
 +
 +            if (needCache) {
 +                // creates an object in the DB for data to be cached
 +                Scope selectedScope = pickCacheScopeForCopy(snapshotInfo, 
destData);
 +
 +                destOnStore = cacheMgr.getCacheObject(snapshotInfo, 
selectedScope);
 +
 +                destOnStore.processEvent(Event.CreateOnlyRequested);
 +            }
 +
 +            usingBackendSnapshot = usingBackendSnapshotFor(snapshotInfo);
 +
 +            if (usingBackendSnapshot) {
 +                final boolean computeClusterSupportsVolumeClone;
 +
 +                // only XenServer, VMware, and KVM are currently supported
 +                if 
(HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
 +                    computeClusterSupportsVolumeClone = 
clusterDao.getSupportsResigning(hostVO.getClusterId());
 +                }
 +                else if 
(HypervisorType.VMware.equals(snapshotInfo.getHypervisorType()) || 
HypervisorType.KVM.equals(snapshotInfo.getHypervisorType())) {
 +                    computeClusterSupportsVolumeClone = true;
 +                }
 +                else {
 +                    throw new CloudRuntimeException("Unsupported hypervisor 
type");
 +                }
 +
 +                if (!computeClusterSupportsVolumeClone) {
 +                    String noSupportForResignErrMsg = "Unable to locate an 
applicable host with which to perform a resignature operation : Cluster ID = " +
 +                            hostVO.getClusterId();
 +
 +                    LOGGER.warn(noSupportForResignErrMsg);
 +
 +                    throw new CloudRuntimeException(noSupportForResignErrMsg);
 +                }
 +            }
 +
 +            String vmdk = null;
 +            String uuid = null;
 +            boolean keepGrantedAccess = false;
 +
 +            DataStore srcDataStore = snapshotInfo.getDataStore();
 +
 +            if (usingBackendSnapshot) {
 +                createVolumeFromSnapshot(snapshotInfo);
 +
 +                if 
(HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType()) || 
HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                    keepGrantedAccess = 
HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType());
 +
 +                    Map<String, String> extraDetails = null;
 +
 +                    if 
(HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                        extraDetails = new HashMap<>();
 +
 +                        String extraDetailsVmdk = 
getSnapshotProperty(snapshotInfo.getId(), DiskTO.VMDK);
 +
 +                        extraDetails.put(DiskTO.VMDK, extraDetailsVmdk);
 +                        extraDetails.put(DiskTO.TEMPLATE_RESIGN, 
Boolean.TRUE.toString());
 +                    }
 +
 +                    copyCmdAnswer = performResignature(snapshotInfo, hostVO, 
extraDetails, keepGrantedAccess);
 +
 +                    // If using VMware, have the host rescan its software HBA 
if dynamic discovery is in use.
 +                    if 
(HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                        String iqn = 
getSnapshotProperty(snapshotInfo.getId(), DiskTO.IQN);
 +
 +                        disconnectHostFromVolume(hostVO, 
srcDataStore.getId(), iqn);
 +                    }
 +
 +                    if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                        if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                            throw new 
CloudRuntimeException(copyCmdAnswer.getDetails());
 +                        } else {
 +                            throw new CloudRuntimeException("Unable to create 
volume from snapshot");
 +                        }
 +                    }
 +
 +                    vmdk = copyCmdAnswer.getNewData().getPath();
 +                    uuid = UUID.randomUUID().toString();
 +                }
 +            }
 +
 +            String value = 
_configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
 +            int primaryStorageDownloadWait = NumbersUtil.parseInt(value, 
Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
 +            CopyCommand copyCommand = new CopyCommand(snapshotInfo.getTO(), 
destOnStore.getTO(), primaryStorageDownloadWait,
 +                    VirtualMachineManager.ExecuteInSequence.value());
 +
 +            try {
 +                if (!keepGrantedAccess) {
 +                    _volumeService.grantAccess(snapshotInfo, hostVO, 
srcDataStore);
 +                }
 +
 +                Map<String, String> srcDetails = 
getSnapshotDetails(snapshotInfo);
 +
 +                if (isForVMware(destData)) {
 +                    srcDetails.put(DiskTO.VMDK, vmdk);
 +                    srcDetails.put(DiskTO.UUID, uuid);
 +
 +                    if (destData instanceof TemplateInfo) {
 +                        VMTemplateVO templateDataStoreVO = 
_vmTemplateDao.findById(destData.getId());
 +
 +                        templateDataStoreVO.setUniqueName(uuid);
 +
 +                        _vmTemplateDao.update(destData.getId(), 
templateDataStoreVO);
 +                    }
 +                }
 +
 +                copyCommand.setOptions(srcDetails);
 +
 +                copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), 
copyCommand);
 +
 +                if (!copyCmdAnswer.getResult()) {
 +                    // We were not able to copy. Handle it.
 +                    errMsg = copyCmdAnswer.getDetails();
 +
 +                    throw new CloudRuntimeException(errMsg);
 +                }
 +
 +                if (needCache) {
 +                    // If cached storage was needed (in case of object store 
as secondary
 +                    // storage), at this point, the data has been copied from 
the primary
 +                    // to the NFS cache by the hypervisor. We now invoke 
another copy
 +                    // command to copy this data from cache to secondary 
storage. We
 +                    // then clean up the cache.
 +
 +                    destOnStore.processEvent(Event.OperationSuccessed, 
copyCmdAnswer);
 +
 +                    CopyCommand cmd = new CopyCommand(destOnStore.getTO(), 
destData.getTO(), primaryStorageDownloadWait,
 +                            VirtualMachineManager.ExecuteInSequence.value());
 +                    EndPoint ep = selector.select(destOnStore, destData);
 +
 +                    if (ep == null) {
 +                        errMsg = "No remote endpoint to send command, check 
if host or SSVM is down";
 +
 +                        LOGGER.error(errMsg);
 +
 +                        copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +                    } else {
 +                        copyCmdAnswer = (CopyCmdAnswer)ep.sendMessage(cmd);
 +                    }
 +
 +                    // clean up snapshot copied to staging
 +                    cacheMgr.deleteCacheObject(destOnStore);
 +                }
 +            } catch (CloudRuntimeException | AgentUnavailableException | 
OperationTimedoutException ex) {
 +                String msg = "Failed to create template from snapshot 
(Snapshot ID = " + snapshotInfo.getId() + ") : ";
 +
 +                LOGGER.warn(msg, ex);
 +
 +                throw new CloudRuntimeException(msg + ex.getMessage(), ex);
 +            } finally {
 +                _volumeService.revokeAccess(snapshotInfo, hostVO, 
srcDataStore);
 +
 +                // If using VMware, have the host rescan its software HBA if 
dynamic discovery is in use.
 +                if 
(HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                    String iqn = getSnapshotProperty(snapshotInfo.getId(), 
DiskTO.IQN);
 +
 +                    disconnectHostFromVolume(hostVO, srcDataStore.getId(), 
iqn);
 +                }
 +
 +                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                    if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                        errMsg = copyCmdAnswer.getDetails();
 +
 +                        if (needCache) {
 +                            cacheMgr.deleteCacheObject(destOnStore);
 +                        }
 +                    }
 +                    else {
 +                        errMsg = "Unable to create template from snapshot";
 +                    }
 +                }
 +
 +                try {
 +                    if (StringUtils.isEmpty(errMsg)) {
 +                        snapshotInfo.processEvent(Event.OperationSuccessed);
 +                    }
 +                    else {
 +                        snapshotInfo.processEvent(Event.OperationFailed);
 +                    }
 +                }
 +                catch (Exception ex) {
 +                    LOGGER.warn("Error processing snapshot event: " + 
ex.getMessage(), ex);
 +                }
 +            }
 +        }
 +        catch (Exception ex) {
 +            errMsg = ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            if (usingBackendSnapshot) {
 +                deleteVolumeFromSnapshot(snapshotInfo);
 +            }
 +
 +            if (copyCmdAnswer == null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    /**
 +     * Creates a volume on the storage from a snapshot that resides on the 
secondary storage (archived snapshot).
 +     * @param snapshotInfo snapshot on secondary
 +     * @param volumeInfo volume to be created on the storage
 +     * @param callback for async
 +     */
 +    private void 
handleCreateVolumeFromSnapshotOnSecondaryStorage(SnapshotInfo snapshotInfo, 
VolumeInfo volumeInfo,
 +                                                                  
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +
 +        try {
 +            // at this point, the snapshotInfo and volumeInfo should have the 
same disk offering ID (so either one should be OK to get a DiskOfferingVO 
instance)
 +            DiskOfferingVO diskOffering = 
_diskOfferingDao.findByIdIncludingRemoved(volumeInfo.getDiskOfferingId());
 +            SnapshotVO snapshot = _snapshotDao.findById(snapshotInfo.getId());
 +
 +            // update the volume's hv_ss_reserve (hypervisor snapshot 
reserve) from a disk offering (used for managed storage)
 +            
_volumeService.updateHypervisorSnapshotReserveForVolume(diskOffering, 
volumeInfo.getId(), snapshot.getHypervisorType());
 +
 +            HostVO hostVO;
 +
 +            // create a volume on the storage
 +            AsyncCallFuture<VolumeApiResult> future = 
_volumeService.createVolumeAsync(volumeInfo, volumeInfo.getDataStore());
 +            VolumeApiResult result = future.get();
 +
 +            if (result.isFailed()) {
 +                LOGGER.error("Failed to create a volume: " + 
result.getResult());
 +
 +                throw new CloudRuntimeException(result.getResult());
 +            }
 +
 +            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), 
volumeInfo.getDataStore());
 +            volumeInfo.processEvent(Event.MigrationRequested);
 +            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), 
volumeInfo.getDataStore());
 +
 +            hostVO = getHost(snapshotInfo.getDataCenterId(), 
snapshotInfo.getHypervisorType(), false);
 +
 +            // copy the volume from secondary via the hypervisor
 +            if 
(HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
 +                copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, 
hostVO);
 +            }
 +            else {
 +                copyCmdAnswer = copyImageToVolume(snapshotInfo, volumeInfo, 
hostVO);
 +            }
 +
 +            if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                    throw new 
CloudRuntimeException(copyCmdAnswer.getDetails());
 +                }
 +                else {
 +                    throw new CloudRuntimeException("Unable to create volume 
from snapshot");
 +                }
 +            }
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Copy operation failed in 
'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotOnSecondaryStorage':
 " +
 +                    ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            if (copyCmdAnswer == null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    /**
 +     * Clones a template present on the storage to a new volume and 
resignatures it.
 +     *
 +     * @param templateInfo source template
 +     * @param volumeInfo destination ROOT volume
 +     * @param callback for async
 +     */
 +    private void 
handleCreateVolumeFromTemplateBothOnStorageSystem(TemplateInfo templateInfo, 
VolumeInfo volumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +
 +        try {
 +            Preconditions.checkArgument(templateInfo != null, "Passing 'null' 
to templateInfo of " +
 +                            
"handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
 +            Preconditions.checkArgument(volumeInfo != null, "Passing 'null' 
to volumeInfo of " +
 +                            
"handleCreateVolumeFromTemplateBothOnStorageSystem is not supported.");
 +
 +            verifyFormat(templateInfo.getFormat());
 +
 +            HostVO hostVO = null;
 +
 +            final boolean computeClusterSupportsVolumeClone;
 +
 +            // only XenServer, VMware, and KVM are currently supported
 +            // Leave host equal to null for KVM since we don't need to 
perform a resignature when using that hypervisor type.
 +            if (volumeInfo.getFormat() == ImageFormat.VHD) {
 +                hostVO = getHost(volumeInfo.getDataCenterId(), 
HypervisorType.XenServer, true);
 +
 +                if (hostVO == null) {
 +                    throw new CloudRuntimeException("Unable to locate a host 
capable of resigning in the zone with the following ID: " +
 +                            volumeInfo.getDataCenterId());
 +                }
 +
 +                computeClusterSupportsVolumeClone = 
clusterDao.getSupportsResigning(hostVO.getClusterId());
 +
 +                if (!computeClusterSupportsVolumeClone) {
 +                    String noSupportForResignErrMsg = "Unable to locate an 
applicable host with which to perform a resignature operation : Cluster ID = " +
 +                            hostVO.getClusterId();
 +
 +                    LOGGER.warn(noSupportForResignErrMsg);
 +
 +                    throw new CloudRuntimeException(noSupportForResignErrMsg);
 +                }
 +            }
 +            else if (volumeInfo.getFormat() == ImageFormat.OVA) {
 +                // all VMware hosts support resigning
 +                hostVO = getHost(volumeInfo.getDataCenterId(), 
HypervisorType.VMware, false);
 +
 +                if (hostVO == null) {
 +                    throw new CloudRuntimeException("Unable to locate a host 
capable of resigning in the zone with the following ID: " +
 +                            volumeInfo.getDataCenterId());
 +                }
 +            }
 +
 +            VolumeDetailVO volumeDetail = new 
VolumeDetailVO(volumeInfo.getId(),
 +                    "cloneOfTemplate",
 +                    String.valueOf(templateInfo.getId()),
 +                    false);
 +
 +            volumeDetail = volumeDetailsDao.persist(volumeDetail);
 +
 +            AsyncCallFuture<VolumeApiResult> future = 
_volumeService.createVolumeAsync(volumeInfo, volumeInfo.getDataStore());
 +
 +            int storagePoolMaxWaitSeconds = 
NumbersUtil.parseInt(_configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()),
 3600);
 +
 +            VolumeApiResult result = future.get(storagePoolMaxWaitSeconds, 
TimeUnit.SECONDS);
 +
 +            if (volumeDetail != null) {
 +                volumeDetailsDao.remove(volumeDetail.getId());
 +            }
 +
 +            if (result.isFailed()) {
 +                LOGGER.warn("Failed to create a volume: " + 
result.getResult());
 +
 +                throw new CloudRuntimeException(result.getResult());
 +            }
 +
 +            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), 
volumeInfo.getDataStore());
 +            volumeInfo.processEvent(Event.MigrationRequested);
 +            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), 
volumeInfo.getDataStore());
 +
 +            if (hostVO != null) {
 +                Map<String, String> extraDetails = null;
 +
 +                if 
(HypervisorType.VMware.equals(templateInfo.getHypervisorType())) {
 +                    extraDetails = new HashMap<>();
 +
 +                    String extraDetailsVmdk = templateInfo.getUniqueName() + 
".vmdk";
 +
 +                    extraDetails.put(DiskTO.VMDK, extraDetailsVmdk);
 +                    extraDetails.put(DiskTO.EXPAND_DATASTORE, 
Boolean.TRUE.toString());
 +                }
 +
 +                copyCmdAnswer = performResignature(volumeInfo, hostVO, 
extraDetails);
 +
 +                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                    if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                        throw new 
CloudRuntimeException(copyCmdAnswer.getDetails());
 +                    } else {
 +                        throw new CloudRuntimeException("Unable to create a 
volume from a template");
 +                    }
 +                }
 +
 +                // If using VMware, have the host rescan its software HBA if 
dynamic discovery is in use.
 +                if 
(HypervisorType.VMware.equals(templateInfo.getHypervisorType())) {
 +                    disconnectHostFromVolume(hostVO, volumeInfo.getPoolId(), 
volumeInfo.get_iScsiName());
 +                }
 +            }
 +            else {
 +                VolumeObjectTO newVolume = new VolumeObjectTO();
 +
 +                newVolume.setSize(volumeInfo.getSize());
 +                newVolume.setPath(volumeInfo.getPath());
 +                newVolume.setFormat(volumeInfo.getFormat());
 +
 +                copyCmdAnswer = new CopyCmdAnswer(newVolume);
 +            }
 +        } catch (Exception ex) {
 +            try {
 +                
volumeInfo.getDataStore().getDriver().deleteAsync(volumeInfo.getDataStore(), 
volumeInfo, null);
 +            }
 +            catch (Exception exc) {
 +                LOGGER.warn("Failed to delete volume", exc);
 +            }
 +
 +            if (templateInfo != null) {
 +                errMsg = "Create volume from template (ID = " + 
templateInfo.getId() + ") failed: " + ex.getMessage();
 +            }
 +            else {
 +                errMsg = "Create volume from template failed: " + 
ex.getMessage();
 +            }
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            if (copyCmdAnswer == null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private void 
handleCreateVolumeFromSnapshotBothOnStorageSystem(SnapshotInfo snapshotInfo, 
VolumeInfo volumeInfo,
 +                                                                   
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +
 +        try {
 +            verifyFormat(snapshotInfo);
 +
 +            HostVO hostVO = getHost(snapshotInfo);
 +
 +            boolean usingBackendSnapshot = 
usingBackendSnapshotFor(snapshotInfo);
 +            boolean computeClusterSupportsVolumeClone = true;
 +
 +            if 
(HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
 +                computeClusterSupportsVolumeClone = 
clusterDao.getSupportsResigning(hostVO.getClusterId());
 +
 +                if (usingBackendSnapshot && 
!computeClusterSupportsVolumeClone) {
 +                    String noSupportForResignErrMsg = "Unable to locate an 
applicable host with which to perform a resignature operation : Cluster ID = " +
 +                            hostVO.getClusterId();
 +
 +                    LOGGER.warn(noSupportForResignErrMsg);
 +
 +                    throw new CloudRuntimeException(noSupportForResignErrMsg);
 +                }
 +            }
 +
 +            boolean canStorageSystemCreateVolumeFromVolume = 
canStorageSystemCreateVolumeFromVolume(snapshotInfo);
 +            boolean useCloning = usingBackendSnapshot || 
(canStorageSystemCreateVolumeFromVolume && computeClusterSupportsVolumeClone);
 +
 +            VolumeDetailVO volumeDetail = null;
 +
 +            if (useCloning) {
 +                volumeDetail = new VolumeDetailVO(volumeInfo.getId(),
 +                    "cloneOfSnapshot",
 +                    String.valueOf(snapshotInfo.getId()),
 +                    false);
 +
 +                volumeDetail = volumeDetailsDao.persist(volumeDetail);
 +            }
 +
 +            // at this point, the snapshotInfo and volumeInfo should have the 
same disk offering ID (so either one should be OK to get a DiskOfferingVO 
instance)
 +            DiskOfferingVO diskOffering = 
_diskOfferingDao.findByIdIncludingRemoved(volumeInfo.getDiskOfferingId());
 +            SnapshotVO snapshot = _snapshotDao.findById(snapshotInfo.getId());
 +
 +            // update the volume's hv_ss_reserve (hypervisor snapshot 
reserve) from a disk offering (used for managed storage)
 +            
_volumeService.updateHypervisorSnapshotReserveForVolume(diskOffering, 
volumeInfo.getId(), snapshot.getHypervisorType());
 +
 +            AsyncCallFuture<VolumeApiResult> future = 
_volumeService.createVolumeAsync(volumeInfo, volumeInfo.getDataStore());
 +            VolumeApiResult result = future.get();
 +
 +            if (volumeDetail != null) {
 +                volumeDetailsDao.remove(volumeDetail.getId());
 +            }
 +
 +            if (result.isFailed()) {
 +                LOGGER.warn("Failed to create a volume: " + 
result.getResult());
 +
 +                throw new CloudRuntimeException(result.getResult());
 +            }
 +
 +            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), 
volumeInfo.getDataStore());
 +            volumeInfo.processEvent(Event.MigrationRequested);
 +            volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), 
volumeInfo.getDataStore());
 +
 +            if 
(HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType()) || 
HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                if (useCloning) {
 +                    Map<String, String> extraDetails = null;
 +
 +                    if 
(HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                        extraDetails = new HashMap<>();
 +
 +                        String extraDetailsVmdk = 
getSnapshotProperty(snapshotInfo.getId(), DiskTO.VMDK);
 +
 +                        extraDetails.put(DiskTO.VMDK, extraDetailsVmdk);
 +                    }
 +
 +                    copyCmdAnswer = performResignature(volumeInfo, hostVO, 
extraDetails);
 +
 +                    // If using VMware, have the host rescan its software HBA 
if dynamic discovery is in use.
 +                    if 
(HypervisorType.VMware.equals(snapshotInfo.getHypervisorType())) {
 +                        disconnectHostFromVolume(hostVO, 
volumeInfo.getPoolId(), volumeInfo.get_iScsiName());
 +                    }
 +                } else {
 +                    // asking for a XenServer host here so we don't always 
prefer to use XenServer hosts that support resigning
 +                    // even when we don't need those hosts to do this kind of 
copy work
 +                    hostVO = getHost(snapshotInfo.getDataCenterId(), 
snapshotInfo.getHypervisorType(), false);
 +
 +                    copyCmdAnswer = performCopyOfVdi(volumeInfo, 
snapshotInfo, hostVO);
 +                }
 +
 +                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                    if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                        throw new 
CloudRuntimeException(copyCmdAnswer.getDetails());
 +                    } else {
 +                        throw new CloudRuntimeException("Unable to create 
volume from snapshot");
 +                    }
 +                }
 +            }
 +            else if 
(HypervisorType.KVM.equals(snapshotInfo.getHypervisorType())) {
 +                VolumeObjectTO newVolume = new VolumeObjectTO();
 +
 +                newVolume.setSize(volumeInfo.getSize());
 +                newVolume.setPath(volumeInfo.get_iScsiName());
 +                newVolume.setFormat(volumeInfo.getFormat());
 +
 +                copyCmdAnswer = new CopyCmdAnswer(newVolume);
 +            }
 +            else {
 +                throw new CloudRuntimeException("Unsupported hypervisor 
type");
 +            }
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Copy operation failed in 
'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotBothOnStorageSystem':
 " +
 +                    ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            if (copyCmdAnswer == null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private void handleCreateVolumeFromVolumeOnSecondaryStorage(VolumeInfo 
srcVolumeInfo, VolumeInfo destVolumeInfo,
 +                                                                long 
dataCenterId, HypervisorType hypervisorType,
 +                                                                
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +
 +        try {
 +            // create a volume on the storage
 +            
destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(),
 destVolumeInfo, null);
 +
 +            destVolumeInfo = 
_volumeDataFactory.getVolume(destVolumeInfo.getId(), 
destVolumeInfo.getDataStore());
 +
 +            HostVO hostVO = getHost(dataCenterId, hypervisorType, false);
 +
 +            // copy the volume from secondary via the hypervisor
 +            copyCmdAnswer = copyImageToVolume(srcVolumeInfo, destVolumeInfo, 
hostVO);
 +
 +            if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                    throw new 
CloudRuntimeException(copyCmdAnswer.getDetails());
 +                }
 +                else {
 +                    throw new CloudRuntimeException("Unable to create volume 
from volume");
 +                }
 +            }
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Copy operation failed in 
'StorageSystemDataMotionStrategy.handleCreateVolumeFromVolumeOnSecondaryStorage':
 " +
 +                    ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            if (copyCmdAnswer == null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private CopyCmdAnswer copyImageToVolume(DataObject srcDataObject, 
VolumeInfo destVolumeInfo, HostVO hostVO) {
 +        String value = 
_configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
 +        int primaryStorageDownloadWait = NumbersUtil.parseInt(value, 
Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
 +
 +        CopyCommand copyCommand = new CopyCommand(srcDataObject.getTO(), 
destVolumeInfo.getTO(), primaryStorageDownloadWait,
 +                VirtualMachineManager.ExecuteInSequence.value());
 +
 +        CopyCmdAnswer copyCmdAnswer;
 +
 +        try {
 +            _volumeService.grantAccess(destVolumeInfo, hostVO, 
destVolumeInfo.getDataStore());
 +
 +            Map<String, String> destDetails = 
getVolumeDetails(destVolumeInfo);
 +
 +            copyCommand.setOptions2(destDetails);
 +
 +            copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), 
copyCommand);
 +        }
 +        catch (CloudRuntimeException | AgentUnavailableException | 
OperationTimedoutException ex) {
 +            String msg = "Failed to copy image : ";
 +
 +            LOGGER.warn(msg, ex);
 +
 +            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
 +        }
 +        finally {
 +            _volumeService.revokeAccess(destVolumeInfo, hostVO, 
destVolumeInfo.getDataStore());
 +        }
 +
 +        VolumeObjectTO volumeObjectTO = 
(VolumeObjectTO)copyCmdAnswer.getNewData();
 +
 +        volumeObjectTO.setFormat(ImageFormat.QCOW2);
 +
 +        return copyCmdAnswer;
 +    }
 +
 +    /**
 +     * If the underlying storage system is making use of read-only snapshots, 
this gives the storage system the opportunity to
 +     * create a volume from the snapshot so that we can copy the VHD file 
that should be inside of the snapshot to secondary storage.
 +     *
 +     * The resultant volume must be writable because we need to resign the SR 
and the VDI that should be inside of it before we copy
 +     * the VHD file to secondary storage.
 +     *
 +     * If the storage system is using writable snapshots, then nothing need 
be done by that storage system here because we can just
 +     * resign the SR and the VDI that should be inside of the snapshot before 
copying the VHD file to secondary storage.
 +     */
 +    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
 +        SnapshotDetailsVO snapshotDetails = 
handleSnapshotDetails(snapshotInfo.getId(), "create");
 +
 +        try {
 +            
snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(),
 snapshotInfo, null);
 +        }
 +        finally {
 +            _snapshotDetailsDao.remove(snapshotDetails.getId());
 +        }
 +    }
 +
 +    /**
 +     * If the underlying storage system needed to create a volume from a 
snapshot for createVolumeFromSnapshot(SnapshotInfo), then
 +     * this is its opportunity to delete that temporary volume and restore 
properties in snapshot_details to the way they were before the
 +     * invocation of createVolumeFromSnapshot(SnapshotInfo).
 +     */
 +    private void deleteVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
 +        SnapshotDetailsVO snapshotDetails = 
handleSnapshotDetails(snapshotInfo.getId(), "delete");
 +
 +        try {
 +            
snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(),
 snapshotInfo, null);
 +        }
 +        finally {
 +            _snapshotDetailsDao.remove(snapshotDetails.getId());
 +        }
 +    }
 +
 +    private SnapshotDetailsVO handleSnapshotDetails(long csSnapshotId, String 
value) {
 +        String name = "tempVolume";
 +
 +        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
 +
 +        SnapshotDetailsVO snapshotDetails = new 
SnapshotDetailsVO(csSnapshotId, name, value, false);
 +
 +        return _snapshotDetailsDao.persist(snapshotDetails);
 +    }
 +
 +    /**
 +     * For each disk to migrate:
 +     *   Create a volume on the target storage system.
 +     *   Make the newly created volume accessible to the target KVM host.
 +     *   Send a command to the target KVM host to connect to the newly 
created volume.
 +     * Send a command to the source KVM host to migrate the VM and its 
storage.
 +     */
 +    @Override
 +    public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, 
VirtualMachineTO vmTO, Host srcHost, Host destHost, 
AsyncCompletionCallback<CopyCommandResult> callback) {
 +        String errMsg = null;
 +
 +        try {
 +            if (srcHost.getHypervisorType() != HypervisorType.KVM) {
 +                throw new CloudRuntimeException("Invalid hypervisor type 
(only KVM supported for this operation at the time being)");
 +            }
 +
 +            verifyLiveMigrationMapForKVM(volumeDataStoreMap);
 +
 +            Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new 
HashMap<>();
 +            Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new 
HashMap<>();
 +
 +            for (Map.Entry<VolumeInfo, DataStore> entry : 
volumeDataStoreMap.entrySet()) {
 +                VolumeInfo srcVolumeInfo = entry.getKey();
 +                DataStore destDataStore = entry.getValue();
 +
 +                VolumeVO srcVolume = 
_volumeDao.findById(srcVolumeInfo.getId());
 +                StoragePoolVO destStoragePool = 
_storagePoolDao.findById(destDataStore.getId());
 +
 +                VolumeVO destVolume = 
duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
 +                VolumeInfo destVolumeInfo = 
_volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
 +
 +                // move the volume from Allocated to Creating
 +                destVolumeInfo.processEvent(Event.MigrationCopyRequested);
 +                // move the volume from Creating to Ready
 +                destVolumeInfo.processEvent(Event.MigrationCopySucceeded);
 +                // move the volume from Ready to Migrating
 +                destVolumeInfo.processEvent(Event.MigrationRequested);
 +
 +                // create a volume on the destination storage
 +                destDataStore.getDriver().createAsync(destDataStore, 
destVolumeInfo, null);
 +
 +                destVolume = _volumeDao.findById(destVolume.getId());
 +
 +                destVolume.setPath(destVolume.get_iScsiName());
 +
 +                _volumeDao.update(destVolume.getId(), destVolume);
 +
 +                destVolumeInfo = 
_volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
 +
 +                _volumeService.grantAccess(destVolumeInfo, destHost, 
destDataStore);
 +
 +                String connectedPath = connectHostToVolume(destHost, 
destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
 +
 +                MigrateCommand.MigrateDiskInfo migrateDiskInfo = new 
MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
 +                        MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
 +                        MigrateCommand.MigrateDiskInfo.DriverType.RAW,
 +                        MigrateCommand.MigrateDiskInfo.Source.DEV,
 +                        connectedPath);
 +
 +                migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
 +
 +                srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, 
destVolumeInfo);
 +            }
 +
 +            PrepareForMigrationCommand pfmc = new 
PrepareForMigrationCommand(vmTO);
 +
 +            try {
 +                Answer pfma = _agentMgr.send(destHost.getId(), pfmc);
 +
 +                if (pfma == null || !pfma.getResult()) {
 +                    String details = pfma != null ? pfma.getDetails() : "null 
answer returned";
 +                    String msg = "Unable to prepare for migration due to the 
following: " + details;
 +
 +                    throw new AgentUnavailableException(msg, 
destHost.getId());
 +                }
 +            }
 +            catch (final OperationTimedoutException e) {
 +                throw new AgentUnavailableException("Operation timed out", 
destHost.getId());
 +            }
 +
 +            VMInstanceVO vm = _vmDao.findById(vmTO.getId());
 +            boolean isWindows = 
_guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
 +
 +            MigrateCommand migrateCommand = new 
MigrateCommand(vmTO.getName(), destHost.getPrivateIpAddress(), isWindows, vmTO, 
true);
 +
 +            
migrateCommand.setWait(StorageManager.KvmStorageOnlineMigrationWait.value());
 +
 +            migrateCommand.setMigrateStorage(migrateStorage);
 +
 +            String autoConvergence = 
_configDao.getValue(Config.KvmAutoConvergence.toString());
 +            boolean kvmAutoConvergence = 
Boolean.parseBoolean(autoConvergence);
 +
 +            migrateCommand.setAutoConvergence(kvmAutoConvergence);
 +
 +            MigrateAnswer migrateAnswer = 
(MigrateAnswer)_agentMgr.send(srcHost.getId(), migrateCommand);
 +
 +            boolean success = migrateAnswer != null && 
migrateAnswer.getResult();
 +
 +            handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, 
destHost);
 +
 +            if (migrateAnswer == null) {
 +                throw new CloudRuntimeException("Unable to get an answer to 
the migrate command");
 +            }
 +
 +            if (!migrateAnswer.getResult()) {
 +                errMsg = migrateAnswer.getDetails();
 +
 +                throw new CloudRuntimeException(errMsg);
 +            }
 +        }
 +        catch (Exception ex) {
 +            errMsg = "Copy operation failed in 
'StorageSystemDataMotionStrategy.copyAsync': " + ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private void handlePostMigration(boolean success, Map<VolumeInfo, 
VolumeInfo> srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host 
destHost) {
 +        if (!success) {
 +            try {
 +                PrepareForMigrationCommand pfmc = new 
PrepareForMigrationCommand(vmTO);
 +
 +                pfmc.setRollback(true);
 +
 +                Answer pfma = _agentMgr.send(destHost.getId(), pfmc);
 +
 +                if (pfma == null || !pfma.getResult()) {
 +                    String details = pfma != null ? pfma.getDetails() : "null 
answer returned";
 +                    String msg = "Unable to rollback prepare for migration 
due to the following: " + details;
 +
 +                    throw new AgentUnavailableException(msg, 
destHost.getId());
 +                }
 +            }
 +            catch (Exception e) {
 +                LOGGER.debug("Failed to disconnect one or more (original) 
dest volumes", e);
 +            }
 +        }
 +
 +        for (Map.Entry<VolumeInfo, VolumeInfo> entry : 
srcVolumeInfoToDestVolumeInfo.entrySet()) {
 +            VolumeInfo srcVolumeInfo = entry.getKey();
 +            VolumeInfo destVolumeInfo = entry.getValue();
 +
 +            if (success) {
 +                srcVolumeInfo.processEvent(Event.OperationSuccessed);
 +                destVolumeInfo.processEvent(Event.OperationSuccessed);
 +
 +                _volumeDao.updateUuid(srcVolumeInfo.getId(), 
destVolumeInfo.getId());
 +
 +                VolumeVO volumeVO = 
_volumeDao.findById(destVolumeInfo.getId());
 +
 +                volumeVO.setFormat(ImageFormat.QCOW2);
 +
 +                _volumeDao.update(volumeVO.getId(), volumeVO);
 +
 +                try {
 +                    _volumeService.destroyVolume(srcVolumeInfo.getId());
 +
 +                    srcVolumeInfo = 
_volumeDataFactory.getVolume(srcVolumeInfo.getId());
 +
 +                    AsyncCallFuture<VolumeApiResult> destroyFuture = 
_volumeService.expungeVolumeAsync(srcVolumeInfo);
 +
 +                    if (destroyFuture.get().isFailed()) {
 +                        LOGGER.debug("Failed to clean up source volume on 
storage");
 +                    }
 +                } catch (Exception e) {
 +                    LOGGER.debug("Failed to clean up source volume on 
storage", e);
 +                }
 +
 +                // Update the volume ID for snapshots on secondary storage
 +                if 
(!_snapshotDao.listByVolumeId(srcVolumeInfo.getId()).isEmpty()) {
 +                    _snapshotDao.updateVolumeIds(srcVolumeInfo.getId(), 
destVolumeInfo.getId());
 +                    
_snapshotDataStoreDao.updateVolumeIds(srcVolumeInfo.getId(), 
destVolumeInfo.getId());
 +                }
 +            }
 +            else {
 +                try {
 +                    disconnectHostFromVolume(destHost, 
destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
 +                }
 +                catch (Exception e) {
 +                    LOGGER.debug("Failed to disconnect (new) dest volume", e);
 +                }
 +
 +                try {
 +                    _volumeService.revokeAccess(destVolumeInfo, destHost, 
destVolumeInfo.getDataStore());
 +                }
 +                catch (Exception e) {
 +                    LOGGER.debug("Failed to revoke access from dest volume", 
e);
 +                }
 +
 +                destVolumeInfo.processEvent(Event.OperationFailed);
 +                srcVolumeInfo.processEvent(Event.OperationFailed);
 +
 +                try {
 +                    _volumeService.destroyVolume(destVolumeInfo.getId());
 +
 +                    destVolumeInfo = 
_volumeDataFactory.getVolume(destVolumeInfo.getId());
 +
 +                    AsyncCallFuture<VolumeApiResult> destroyFuture = 
_volumeService.expungeVolumeAsync(destVolumeInfo);
 +
 +                    if (destroyFuture.get().isFailed()) {
 +                        LOGGER.debug("Failed to clean up dest volume on 
storage");
 +                    }
 +                } catch (Exception e) {
 +                    LOGGER.debug("Failed to clean up dest volume on storage", 
e);
 +                }
 +            }
 +        }
 +    }
 +
 +    private VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, 
StoragePoolVO storagePoolVO) {
 +        Long lastPoolId = volume.getPoolId();
 +
 +        VolumeVO newVol = new VolumeVO(volume);
 +
 +        newVol.setInstanceId(null);
 +        newVol.setChainInfo(null);
 +        newVol.setPath(null);
 +        newVol.setFolder(null);
 +        newVol.setPodId(storagePoolVO.getPodId());
 +        newVol.setPoolId(storagePoolVO.getId());
 +        newVol.setLastPoolId(lastPoolId);
 +
 +        return _volumeDao.persist(newVol);
 +    }
 +
 +    private String connectHostToVolume(Host host, long storagePoolId, String 
iqn) {
 +        ModifyTargetsCommand modifyTargetsCommand = 
getModifyTargetsCommand(storagePoolId, iqn, true);
 +
 +        return sendModifyTargetsCommand(modifyTargetsCommand, 
host.getId()).get(0);
 +    }
 +
 +    private void disconnectHostFromVolume(Host host, long storagePoolId, 
String iqn) {
 +        ModifyTargetsCommand modifyTargetsCommand = 
getModifyTargetsCommand(storagePoolId, iqn, false);
 +
 +        sendModifyTargetsCommand(modifyTargetsCommand, host.getId());
 +    }
 +
 +    private ModifyTargetsCommand getModifyTargetsCommand(long storagePoolId, 
String iqn, boolean add) {
 +        StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
 +
 +        Map<String, String> details = new HashMap<>();
 +
 +        details.put(ModifyTargetsCommand.IQN, iqn);
 +        details.put(ModifyTargetsCommand.STORAGE_TYPE, 
storagePool.getPoolType().name());
 +        details.put(ModifyTargetsCommand.STORAGE_UUID, storagePool.getUuid());
 +        details.put(ModifyTargetsCommand.STORAGE_HOST, 
storagePool.getHostAddress());
 +        details.put(ModifyTargetsCommand.STORAGE_PORT, 
String.valueOf(storagePool.getPort()));
 +
 +        ModifyTargetsCommand cmd = new ModifyTargetsCommand();
 +
 +        List<Map<String, String>> targets = new ArrayList<>();
 +
 +        targets.add(details);
 +
 +        cmd.setTargets(targets);
 +        cmd.setApplyToAllHostsInCluster(true);
 +        cmd.setAdd(add);
 +        
cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
 +
 +        return cmd;
 +    }
 +
 +    private List<String> sendModifyTargetsCommand(ModifyTargetsCommand cmd, 
long hostId) {
 +        ModifyTargetsAnswer modifyTargetsAnswer = 
(ModifyTargetsAnswer)_agentMgr.easySend(hostId, cmd);
 +
 +        if (modifyTargetsAnswer == null) {
 +            throw new CloudRuntimeException("Unable to get an answer to the 
modify targets command");
 +        }
 +
 +        if (!modifyTargetsAnswer.getResult()) {
 +            String msg = "Unable to modify targets on the following host: " + 
hostId;
 +
 +            throw new CloudRuntimeException(msg);
 +        }
 +
 +        return modifyTargetsAnswer.getConnectedPaths();
 +    }
 +
 +    /*
 +    * At a high level: The source storage cannot be managed and the 
destination storage must be managed.
 +    */
 +    private void verifyLiveMigrationMapForKVM(Map<VolumeInfo, DataStore> 
volumeDataStoreMap) {
 +        for (Map.Entry<VolumeInfo, DataStore> entry : 
volumeDataStoreMap.entrySet()) {
 +            VolumeInfo volumeInfo = entry.getKey();
 +
 +            Long storagePoolId = volumeInfo.getPoolId();
 +            StoragePoolVO srcStoragePoolVO = 
_storagePoolDao.findById(storagePoolId);
 +
 +            if (srcStoragePoolVO == null) {
 +                throw new CloudRuntimeException("Volume with ID " + 
volumeInfo.getId() + " is not associated with a storage pool.");
 +            }
 +
 +            if (srcStoragePoolVO.isManaged()) {
 +                throw new CloudRuntimeException("Migrating a volume online 
with KVM from managed storage is not currently supported.");
 +            }
 +
 +            DataStore dataStore = entry.getValue();
 +            StoragePoolVO destStoragePoolVO = 
_storagePoolDao.findById(dataStore.getId());
 +
 +            if (destStoragePoolVO == null) {
 +                throw new CloudRuntimeException("Destination storage pool 
with ID " + dataStore.getId() + " was not located.");
 +            }
 +
 +            if (!destStoragePoolVO.isManaged()) {
 +                throw new CloudRuntimeException("Migrating a volume online 
with KVM can currently only be done when moving to managed storage.");
 +            }
 +        }
 +    }
 +
 +    private boolean canStorageSystemCreateVolumeFromVolume(SnapshotInfo 
snapshotInfo) {
 +        boolean supportsCloningVolumeFromVolume = false;
 +
 +        DataStore dataStore = 
dataStoreMgr.getDataStore(snapshotInfo.getDataStore().getId(), 
DataStoreRole.Primary);
 +
 +        Map<String, String> mapCapabilities = 
dataStore.getDriver().getCapabilities();
 +
 +        if (mapCapabilities != null) {
 +            String value = 
mapCapabilities.get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString());
 +
 +            supportsCloningVolumeFromVolume = Boolean.valueOf(value);
 +        }
 +
 +        return supportsCloningVolumeFromVolume;
 +    }
 +
 +    private String getVolumeProperty(long volumeId, String property) {
 +        VolumeDetailVO volumeDetails = volumeDetailsDao.findDetail(volumeId, 
property);
 +
 +        if (volumeDetails != null) {
 +            return volumeDetails.getValue();
 +        }
 +
 +        return null;
 +    }
 +
 +    private String getSnapshotProperty(long snapshotId, String property) {
 +        SnapshotDetailsVO snapshotDetails = 
_snapshotDetailsDao.findDetail(snapshotId, property);
 +
 +        if (snapshotDetails != null) {
 +            return snapshotDetails.getValue();
 +        }
 +
 +        return null;
 +    }
 +
 +    private void handleCreateTemplateFromVolume(VolumeInfo volumeInfo, 
TemplateInfo templateInfo, AsyncCompletionCallback<CopyCommandResult> callback) 
{
 +        boolean srcVolumeDetached = volumeInfo.getAttachedVM() == null;
 +
 +        String errMsg = null;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +
 +        try {
 +            if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat())) {
 +                throw new CloudRuntimeException("When using managed storage, 
you can only create a template from a volume on KVM currently.");
 +            }
 +
 +            volumeInfo.processEvent(Event.MigrationRequested);
 +
 +            HostVO hostVO = getHost(volumeInfo.getDataCenterId(), 
HypervisorType.KVM, false);
 +            DataStore srcDataStore = volumeInfo.getDataStore();
 +
 +            String value = 
_configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
 +            int primaryStorageDownloadWait = NumberUtils.toInt(value, 
Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
 +            CopyCommand copyCommand = new CopyCommand(volumeInfo.getTO(), 
templateInfo.getTO(), primaryStorageDownloadWait, 
VirtualMachineManager.ExecuteInSequence.value());
 +
 +            try {
 +                if (srcVolumeDetached) {
 +                    _volumeService.grantAccess(volumeInfo, hostVO, 
srcDataStore);
 +                }
 +
 +                Map<String, String> srcDetails = getVolumeDetails(volumeInfo);
 +
 +                copyCommand.setOptions(srcDetails);
 +
 +                copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), 
copyCommand);
 +
 +                if (!copyCmdAnswer.getResult()) {
 +                    // We were not able to copy. Handle it.
 +                    errMsg = copyCmdAnswer.getDetails();
 +                    throw new CloudRuntimeException(errMsg);
 +                }
 +
 +                VMTemplateVO vmTemplateVO = 
_vmTemplateDao.findById(templateInfo.getId());
 +
 +                vmTemplateVO.setHypervisorType(HypervisorType.KVM);
 +
 +                _vmTemplateDao.update(vmTemplateVO.getId(), vmTemplateVO);
 +            }
 +            catch (CloudRuntimeException | AgentUnavailableException | 
OperationTimedoutException ex) {
 +                String msg = "Failed to create template from volume (Volume 
ID = " + volumeInfo.getId() + ") : ";
 +
 +                LOGGER.warn(msg, ex);
 +
 +                throw new CloudRuntimeException(msg + ex.getMessage(), ex);
 +            }
 +            finally {
 +                try {
 +                    if (srcVolumeDetached) {
 +                        _volumeService.revokeAccess(volumeInfo, hostVO, 
srcDataStore);
 +                    }
 +                }
 +                catch (Exception ex) {
 +                    LOGGER.warn("Error revoking access to volume (Volume ID = 
" + volumeInfo.getId() + "): " + ex.getMessage(), ex);
 +                }
 +                if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
 +                    if (copyCmdAnswer != null && 
!StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
 +                        errMsg = copyCmdAnswer.getDetails();
 +                    }
 +                    else {
 +                        errMsg = "Unable to create template from volume";
 +                    }
 +                }
 +
 +                try {
 +                    if (StringUtils.isEmpty(errMsg)) {
 +                        volumeInfo.processEvent(Event.OperationSuccessed);
 +                    }
 +                    else {
 +                        volumeInfo.processEvent(Event.OperationFailed);
 +                    }
 +                }
 +                catch (Exception ex) {
 +                    LOGGER.warn("Error processing snapshot event: " + 
ex.getMessage(), ex);
 +                }
 +            }
 +        }
 +        catch (Exception ex) {
 +            errMsg = ex.getMessage();
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +        finally {
 +            if (copyCmdAnswer == null) {
 +                copyCmdAnswer = new CopyCmdAnswer(errMsg);
 +            }
 +
 +            CopyCommandResult result = new CopyCommandResult(null, 
copyCmdAnswer);
 +
 +            result.setResult(errMsg);
 +
 +            callback.complete(result);
 +        }
 +    }
 +
 +    private Map<String, String> getVolumeDetails(VolumeInfo volumeInfo) {
 +        long storagePoolId = volumeInfo.getPoolId();
 +        StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
 +
 +        if (!storagePoolVO.isManaged()) {
 +            return null;
 +        }
 +
 +        Map<String, String> volumeDetails = new HashMap<>();
 +
 +        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
 +
 +        volumeDetails.put(DiskTO.STORAGE_HOST, 
storagePoolVO.getHostAddress());
 +        volumeDetails.put(DiskTO.STORAGE_PORT, 
String.valueOf(storagePoolVO.getPort()));
 +        volumeDetails.put(DiskTO.IQN, volumeVO.get_iScsiName());
 +
 +        volumeDetails.put(DiskTO.VOLUME_SIZE, 
String.valueOf(volumeVO.getSize()));
 +        volumeDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, 
getVolumeProperty(volumeInfo.getId(), DiskTO.SCSI_NAA_DEVICE_ID));
 +
 +        ChapInfo chapInfo = _volumeService.getChapInfo(volumeInfo, 
volumeInfo.getDataStore());
 +
 +        if (chapInfo != null) {
 +            volumeDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, 
chapInfo.getInitiatorUsername());
 +            volumeDetails.put(DiskTO.CHAP_INITIATOR_SECRET, 
chapInfo.getInitiatorSecret());
 +            volumeDetails.put(DiskTO.CHAP_TARGET_USERNAME, 
chapInfo.getTargetUsername());
 +            volumeDetails.put(DiskTO.CHAP_TARGET_SECRET, 
chapInfo.getTargetSecret());
 +        }
 +
 +        return volumeDetails;
 +    }
 +
 +    private Map<String, String> getSnapshotDetails(SnapshotInfo snapshotInfo) 
{
 +        Map<String, String> snapshotDetails = new HashMap<>();
 +
 +        long storagePoolId = snapshotInfo.getDataStore().getId();
 +        StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
 +
 +        snapshotDetails.put(DiskTO.STORAGE_HOST, 
storagePoolVO.getHostAddress());
 +        snapshotDetails.put(DiskTO.STORAGE_PORT, 
String.valueOf(storagePoolVO.getPort()));
 +
 +        long snapshotId = snapshotInfo.getId();
 +
 +        snapshotDetails.put(DiskTO.IQN, getSnapshotProperty(snapshotId, 
DiskTO.IQN));
 +        snapshotDetails.put(DiskTO.VOLUME_SIZE, 
String.valueOf(snapshotInfo.getSize()));
 +        snapshotDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, 
getSnapshotProperty(snapshotId, DiskTO.SCSI_NAA_DEVICE_ID));
 +
 +        snapshotDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, 
getSnapshotProperty(snapshotId, DiskTO.CHAP_INITIATOR_USERNAME));
 +        snapshotDetails.put(DiskTO.CHAP_INITIATOR_SECRET, 
getSnapshotProperty(snapshotId, DiskTO.CHAP_INITIATOR_SECRET));
 +        snapshotDetails.put(DiskTO.CHAP_TARGET_USERNAME, 
getSnapshotProperty(snapshotId, DiskTO.CHAP_TARGET_USERNAME));
 +        snapshotDetails.put(DiskTO.CHAP_TARGET_SECRET, 
getSnapshotProperty(snapshotId, DiskTO.CHAP_TARGET_SECRET));
 +
 +        return snapshotDetails;
 +    }
 +
 +    private HostVO getHost(SnapshotInfo snapshotInfo) {
 +        HypervisorType hypervisorType = snapshotInfo.getHypervisorType();
 +
 +        if (HypervisorType.XenServer.equals(hypervisorType)) {
 +            HostVO hostVO = getHost(snapshotInfo.getDataCenterId(), 
hypervisorType, true);
 +
 +            if (hostVO == null) {
 +                hostVO = getHost(snapshotInfo.getDataCenterId(), 
hypervisorType, false);
 +
 +                if (hostVO == null) {
 +                    throw new CloudRuntimeException("Unable to locate an 
applicable host in data center with ID = " + snapshotInfo.getDataCenterId());
 +                }
 +            }
 +
 +            return hostVO;
 +        }
 +
 +        if (HypervisorType.VMware.equals(hypervisorType) || 
HypervisorType.KVM.equals(hypervisorType)) {
 +            return getHost(snapshotInfo.getDataCenterId(), hypervisorType, 
false);
 +        }
 +
 +        throw new CloudRuntimeException("Unsupported hypervisor type");
 +    }
 +
 +    private HostVO getHostInCluster(long clusterId) {
 +        List<HostVO> hosts = _hostDao.findByClusterId(clusterId);
 +
 +        if (hosts != null && hosts.size() > 0) {
 +            Collections.shuffle(hosts, RANDOM);
 +
 +            for (HostVO host : hosts) {
 +                if (ResourceState.Enabled.equals(host.getResourceState())) {
 +                    return host;
 +                }
 +            }
 +        }
 +
 +        throw new CloudRuntimeException("Unable to locate a host");
 +    }
 +
 +    private HostVO getHost(Long zoneId, HypervisorType hypervisorType, 
boolean computeClusterMustSupportResign) {
 +        Preconditions.checkArgument(zoneId != null, "Zone ID cannot be 
null.");
 +        Preconditions.checkArgument(hypervisorType != null, "Hypervisor type 
cannot be null.");
 +
 +        List<HostVO> hosts = 
_hostDao.listByDataCenterIdAndHypervisorType(zoneId, hypervisorType);
 +
 +        if (hosts == null) {
 +            return null;
 +        }
 +
 +        List<Long> clustersToSkip = new ArrayList<>();
 +
 +        Collections.shuffle(hosts, RANDOM);
 +
 +        for (HostVO host : hosts) {
 +            if (!ResourceState.Enabled.equals(host.getResourceState())) {
 +                continue;
 +            }
 +
 +            if (computeClusterMustSupportResign) {
 +                long clusterId = host.getClusterId();
 +
 +                if (clustersToSkip.contains(clusterId)) {
 +                    continue;
 +                }
 +
 +                if (clusterDao.getSupportsResigning(clusterId)) {
 +                    return host;
 +                }
 +                else {
 +                    clustersToSkip.add(clusterId);
 +                }
 +            }
 +            else {
 +                return host;
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    private Map<String, String> getDetails(DataObject dataObj) {
 +        if (dataObj instanceof VolumeInfo) {
 +            return getVolumeDetails((VolumeInfo)dataObj);
 +        }
 +        else if (dataObj instanceof SnapshotInfo) {
 +            return getSnapshotDetails((SnapshotInfo)dataObj);
 +        }
 +
 +        throw new CloudRuntimeException("'dataObj' must be of type 
'VolumeInfo' or 'SnapshotInfo'.");
 +    }
 +
 +    private boolean isForVMware(DataObject dataObj) {
 +        if (dataObj instanceof VolumeInfo) {
 +            return ImageFormat.OVA.equals(((VolumeInfo)dataObj).getFormat());
 +        }
 +
 +        if (dataObj instanceof SnapshotInfo) {
 +            return 
ImageFormat.OVA.equals(((SnapshotInfo)dataObj).getBaseVolume().getFormat());
 +        }
 +
 +        return dataObj instanceof TemplateInfo && 
HypervisorType.VMware.equals(((TemplateInfo)dataObj).getHypervisorType());
 +    }
 +
 +    private CopyCmdAnswer performResignature(DataObject dataObj, HostVO 
hostVO, Map<String, String> extraDetails) {
 +        return performResignature(dataObj, hostVO, extraDetails, false);
 +    }
 +
 +    private CopyCmdAnswer performResignature(DataObject dataObj, HostVO 
hostVO, Map<String, String> extraDetails, boolean keepGrantedAccess) {
 +        long storagePoolId = dataObj.getDataStore().getId();
 +        DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, 
DataStoreRole.Primary);
 +
 +        Map<String, String> details = getDetails(dataObj);
 +
 +        if (extraDetails != null) {
 +            details.putAll(extraDetails);
 +        }
 +
 +        ResignatureCommand command = new ResignatureCommand(details);
 +
 +        ResignatureAnswer answer;
 +
 +        GlobalLock lock = GlobalLock.getInternLock(dataStore.getUuid());
 +
 +        if (!lock.lock(LOCK_TIME_IN_SECONDS)) {
 +            String errMsg = "Couldn't lock the DB (in performResignature) on 
the following string: " + dataStore.getUuid();
 +
 +            LOGGER.warn(errMsg);
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +
 +        try {
 +            _volumeService.grantAccess(dataObj, hostVO, dataStore);
 +
 +            answer = (ResignatureAnswer)_agentMgr.send(hostVO.getId(), 
command);
 +        }
 +        catch (CloudRuntimeException | AgentUnavailableException | 
OperationTimedoutException ex) {
 +            keepGrantedAccess = false;
 +
 +            String msg = "Failed to resign the DataObject with the following 
ID: " + dataObj.getId();
 +
 +            LOGGER.warn(msg, ex);
 +
 +            throw new CloudRuntimeException(msg + ex.getMessage());
 +        }
 +        finally {
 +            lock.unlock();
 +            lock.releaseRef();
 +
 +            if (!keepGrantedAccess) {
 +                _volumeService.revokeAccess(dataObj, hostVO, dataStore);
 +            }
 +        }
 +
 +        if (answer == null || !answer.getResult()) {
 +            final String errMsg;
 +
 +            if (answer != null && answer.getDetails() != null && 
!answer.getDetails().isEmpty()) {
 +                errMsg = answer.getDetails();
 +            }
 +            else {
 +                errMsg = "Unable to perform resignature operation in 
'StorageSystemDataMotionStrategy.performResignature'";
 +            }
 +
 +            throw new CloudRuntimeException(errMsg);
 +        }
 +
 +        VolumeObjectTO newVolume = new VolumeObjectTO();
 +
 +        newVolume.setSize(answer.getSize());
 +        newVolume.setPath(answer.getPath());
 +        newVolume.setFormat(answer.getFormat());
 +
 +        return new CopyCmdAnswer(newVolume);
 +    }
 +
 +    private DataObject cacheSnapshotChain(SnapshotInfo snapshot, Scope scope) 
{
 +        DataObject leafData = null;
 +        DataStore store = cacheMgr.getCacheStorage(snapshot, scope);
 +
 +        while (snapshot != null) {
 +            DataObject cacheData = cacheMgr.createCacheObject(snapshot, 
store);
 +
 +            if (leafData == null) {
 +                leafData = cacheData;
 +            }
 +
 +            snapshot = snapshot.getParent();
 +        }
 +
 +        return leafData;
 +    }
 +
 +    private String migrateVolume(VolumeInfo srcVolumeInfo, VolumeInfo 
destVolumeInfo, HostVO hostVO, String errMsg) {
 +        boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
 +
 +        try {
 +            Map<String, String> srcDetails = getVolumeDetails(srcVolumeInfo);
 +            Map<String, String> destDetails = 
getVolumeDetails(destVolumeInfo);
 +
 +            MigrateVolumeCommand migrateVolumeCommand = new 
MigrateVolumeCommand(srcVolumeInfo.getTO(), destVolumeInfo.getTO(),
 +                    srcDetails, destDetails, 
StorageManager.KvmStorageOfflineMigrationWait.value());
 +
 +            if (srcVolumeDetached) {
 +                _volumeService.grantAccess(srcVolumeInfo, hostVO, 
srcVolumeInfo.getDataStore());
 +            }
 +
 +            _volumeService.grantAccess(destVolumeInfo, hostVO, 
destVolumeInfo.getDataStore());
 +
 +            MigrateVolumeAnswer migrateVolumeAnswer = 
(MigrateVolumeAnswer)_agentMgr.send(hostVO.getId(), migrateVolumeCommand);
 +
 +            if (migrateVolumeAnswer == null || 
!migrateVolumeAnswer.getResult()) {
 +                if (migrateVolumeAnswer != null && 
!StringUtils.isEmpty(migrateVolumeAnswer.getDetails())) {
 +                    throw new 
CloudRuntimeException(migrateVolumeAnswer.getDetails());
 +                }
 +                else {
 +                    throw new CloudRuntimeException(errMsg);
 +                }
 +            }
 +
 +            if (srcVolumeDetached) {
 +                _volumeService.revokeAccess(destVolumeInfo, hostVO, 
destVolumeInfo.getDataStore());
 +            }
 +
 +            try {
 +                _volumeService.revokeAccess(srcVolumeInfo, hostVO, 
srcVolumeInfo.getDataStore());
 +            }
 +            catch (Exception e) {
 +                // This volume should be deleted soon, so just log a warning 
here.
 +                LOGGER.warn(e.getMessage(), e);
 +            }
 +
 +            return migrateVolumeAnswer.getVolumePath();
 +        }
 +        catch (Exception ex) {
 +            try {
 +                _volumeService.revokeAccess(destVolumeInfo, hostVO, 
destVolumeInfo.getDataStore());
 +            }
 +            catch (Exception e) {
 +                // This volume should be deleted soon, so just log a warning 
here.
 +                LOGGER.warn(e.getMessage(), e);
 +            }
 +
 +            if (srcVolumeDetached) {
 +                _volumeService.revokeAccess(srcVolumeInfo, hostVO, 
srcVolumeInfo.getDataStore());
 +            }
 +
 +            String msg = "Failed to perform volume migration : ";
 +
 +            LOGGER.warn(msg, ex);
 +
 +            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
 +        }
 +    }
 +
 +    private String copyVolumeToSecondaryStorage(VolumeInfo srcVolumeInfo, 
VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
 +        boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
 +
 +        try {
 +            StoragePoolVO storagePoolVO = 
_storagePoolDao.findById(srcVolumeInfo.getPoolId());
 +            Map<String, String> srcDetails = getVolumeDetails(srcVolumeInfo);
 +
 +            CopyVolumeCommand copyVolumeCommand = new 
CopyVolumeCommand(srcVolumeInfo.getId(), destVolumeInfo.getPath(), 
storagePoolVO,
 +                    destVolumeInfo.getDataStore().getUri(), true, 
StorageManager.KvmStorageOfflineMigrationWait.value(), true);
 +
 +            copyVolumeCommand.setSrcData(srcVolumeInfo.getTO());
 +            copyVolumeCommand.setSrcDetails(srcDetails);
 +
 +            if (srcVolumeDetached) {
 +                _volumeService.grantAccess(srcVolumeInfo, hostVO, 
srcVolumeInfo.getDataStore());
 +            }
 +
 +            CopyVolumeAnswer copyVolumeAnswer = 
(CopyVolumeAnswer)_agentMgr.send(hostVO.getId(), copyVolumeCommand);
 +
 +            if (copyVolumeAnswer == null || !copyVolumeAnswer.getResult()) {
 +                if (copyVolumeAnswer != null && 
!StringUtils.isEmpty(copyVolumeAnswer.getDetails())) {
 +                    throw new 
CloudRuntimeException(copyVolumeAnswer.getDetails());
 +                }
 +                else {
 +                    throw new CloudRuntimeException(errMsg);
 +                }
 +            }
 +
 +            return copyVolumeAnswer.getVolumePath();
 +        }
 +        catch (Exception ex) {
 +            String msg = "Failed to perform volume copy to secondary storage 
: ";
 +
 +            LOGGER.warn(msg, ex);
 +
 +            throw new CloudRuntimeException(msg + ex.getMessage());
 +        }
 +        finally {
 +            if (srcVolumeDetached) {
 +                _volumeService.revokeAccess(srcVolumeInfo, hostVO, 
srcVolumeInfo.getDataStore());
 +            }
 +        }
 +    }
 +
 +    private void setCertainVolumeValuesNull(long volumeId) {
 +        VolumeVO volumeVO = _volumeDao.findById(volumeId);
 +
 +        volumeVO.set_iScsiName(null);
 +        volumeVO.setMinIops(null);
 +        volumeVO.setMaxIops(null);
 +        volumeVO.setHypervisorSnapshotReserve(null);
 +
 +        _volumeDao.update(volumeId, volumeVO);
 +    }
 +
 +    private void updateVolumePath(long volumeId, String path) {
 +        VolumeVO volumeVO = _volumeDao.findById(volumeId);
 +
 +        volumeVO.setPath(path);
 +
 +        _volumeDao.update(volumeId, volumeVO);
 +    }
 +
 +    /**
 +     * Copies data from secondary storage to a primary volume
 +     * @param volumeInfo The primary volume
 +     * @param snapshotInfo  destination of the copy
 +     * @param hostVO the host used to copy the data
 +     * @return result of the copy
 +     */
 +    private CopyCmdAnswer performCopyOfVdi(VolumeInfo volumeInfo, 
SnapshotInfo snapshotInfo, HostVO hostVO) {
 +        Snapshot.LocationType locationType = snapshotInfo.getLocationType();
 +
 +        String value = 
_configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
 +        int primaryStorageDownloadWait = NumbersUtil.parseInt(value, 
Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
 +
 +        DataObject srcData = snapshotInfo;
 +        CopyCmdAnswer copyCmdAnswer = null;
 +        DataObject cacheData = null;
 +
 +        boolean needCacheStorage = needCacheStorage(snapshotInfo, volumeInfo);
 +
 +        if (needCacheStorage) {
 +            cacheData = cacheSnapshotChain(snapshotInfo, new 
ZoneScope(volumeInfo.getDataCenterId()));
 +            srcData = cacheData;
 +        }
 +
 +        CopyCommand copyCommand = new CopyCommand(srcData.getTO(), 
volumeInfo.getTO(), primaryStorageDownloadWait, 
VirtualMachineManager.ExecuteInSequence.value());
 +
 +        try {
 +            if (Snapshot.LocationType.PRIMARY.equals(locationType)) {
 +                _volumeService.grantAccess(snapshotInfo, hostVO, 
snapshotInfo.getDataStore());
 +
 +                Map<String, String> srcDetails = 
getSnapshotDetails(snapshotInfo);
 +
 +                copyCommand.setOptions(srcDetails);
 +            }
 +
 +            _volumeService.grantAccess(volumeInfo, hostVO, 
volumeInfo.getDataStore());
 +
 +            Map<String, String> destDetails = getVolumeDetails(volumeInfo);
 +
 +            copyCommand.setOptions2(destDetails);
 +
 +            copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), 
copyCommand);
 +        }
 +        catch (CloudRuntimeException | AgentUnavailableException | 
OperationTimedoutException ex) {
 +            String msg = "Failed to perform VDI copy : ";
 +
 +            LOGGER.warn(msg, ex);
 +
 +            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
 +        }
 +        finally {
 +            if (Snapshot.LocationType.PRIMARY.equals(locationType)) {
 +                _volumeService.revokeAccess(snapshotInfo, hostVO, 
snapshotInfo.getDataStore());
 +            }
 +
 +            _volumeService.revokeAccess(volumeInfo, hostVO, 
volumeInfo.getDataStore());
 +
 +            if (needCacheStorage && copyCmdAnswer != null && 
copyCmdAnswer.getResult()) {
 +                cacheMgr.deleteCacheObject(cacheData);
 +            }
 +        }
 +
 +        return copyCmdAnswer;
 +    }
 +}
diff --cc 
engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
index 0000000,ec85f7d..ec85f7d
mode 000000,100644..100644
--- 
a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java
+++ 
b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java

Reply via email to