content: /*
   * 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.datastore.driver;
  
  import com.cloud.storage.dao.SnapshotDetailsVO;
  
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import javax.inject.Inject;
  
  import com.cloud.storage.dao.StoragePoolHostDao;
  import com.cloud.storage.dao.VMTemplateDetailsDao;
  import com.cloud.storage.dao.VolumeDao;
  import com.cloud.storage.dao.VolumeDetailsDao;
  import com.cloud.tags.dao.ResourceTagDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.VirtualMachine.State;
  import com.cloud.vm.VirtualMachineManager;
  import com.cloud.vm.dao.VMInstanceDao;
  
  import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
  import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
  import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
  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.ObjectInDataStoreStateMachine;
  import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
  import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
  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.framework.async.AsyncCompletionCallback;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
  import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
  import org.apache.cloudstack.storage.RemoteHostEndPoint;
  import org.apache.cloudstack.storage.command.CommandResult;
  import org.apache.cloudstack.storage.command.CopyCmdAnswer;
  import org.apache.cloudstack.storage.command.CreateObjectAnswer;
  import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
  import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef;
  import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef;
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
  import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
  import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
  import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
  import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
  import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
  import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
  import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
  import org.apache.cloudstack.storage.to.SnapshotObjectTO;
  import org.apache.cloudstack.storage.to.TemplateObjectTO;
  import org.apache.cloudstack.storage.to.VolumeObjectTO;
  import org.apache.cloudstack.storage.volume.VolumeObject;
  
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.collections4.MapUtils;
  
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.storage.ResizeVolumeAnswer;
  import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
  import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
  import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
  import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
  import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
  import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
  import com.cloud.agent.api.storage.StorPoolSetVolumeEncryptionAnswer;
  import com.cloud.agent.api.storage.StorPoolSetVolumeEncryptionCommand;
  import com.cloud.agent.api.to.DataObjectType;
  import com.cloud.agent.api.to.DataStoreTO;
  import com.cloud.agent.api.to.DataTO;
  import com.cloud.agent.api.to.StorageFilerTO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.exception.StorageUnavailableException;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
  import com.cloud.offering.DiskOffering;
  import com.cloud.server.ResourceTag;
  import com.cloud.server.ResourceTag.ResourceObjectType;
  import com.cloud.service.ServiceOfferingDetailsVO;
  import com.cloud.service.ServiceOfferingVO;
  import com.cloud.service.dao.ServiceOfferingDao;
  import com.cloud.service.dao.ServiceOfferingDetailsDao;
  import com.cloud.storage.DataStoreRole;
  import com.cloud.storage.ResizeVolumePayload;
  import com.cloud.storage.Snapshot;
  import com.cloud.storage.SnapshotVO;
  import com.cloud.storage.Storage.StoragePoolType;
  import com.cloud.storage.StorageManager;
  import com.cloud.storage.StoragePool;
  import com.cloud.storage.VMTemplateDetailVO;
  import com.cloud.storage.VMTemplateStoragePoolVO;
  import com.cloud.storage.Volume;
  import com.cloud.storage.VolumeDetailVO;
  import com.cloud.storage.VolumeVO;
  import com.cloud.storage.dao.SnapshotDao;
  import com.cloud.storage.dao.SnapshotDetailsDao;
  
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  
  public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
  
      protected Logger logger = LogManager.getLogger(getClass());
  
      @Inject
      private VolumeDao volumeDao;
      @Inject
      private StorageManager storageMgr;
      @Inject
      private PrimaryDataStoreDao primaryStoreDao;
      @Inject
      private EndPointSelector selector;
      @Inject
      private ConfigurationDao configDao;
      @Inject
      private TemplateDataStoreDao vmTemplateDataStoreDao;
      @Inject
      private VMInstanceDao vmInstanceDao;
      @Inject
      private ClusterDao clusterDao;
      @Inject
      private HostDao hostDao;
      @Inject
      private ResourceTagDao resourceTagDao;
      @Inject
      private SnapshotDetailsDao snapshotDetailsDao;
      @Inject
      private SnapshotDao snapshotDao;
      @Inject
      private SnapshotDataStoreDao snapshotDataStoreDao;
      @Inject
      private VolumeDetailsDao volumeDetailsDao;
      @Inject
      private VMTemplateDetailsDao vmTemplateDetailsDao;
      @Inject
      private StoragePoolDetailsDao storagePoolDetailsDao;
      @Inject
      private StoragePoolHostDao storagePoolHostDao;
      @Inject
      DataStoreManager dataStoreManager;
      @Inject
      private DiskOfferingDetailsDao diskOfferingDetailsDao;
      @Inject
      private ServiceOfferingDetailsDao serviceOfferingDetailDao;
      @Inject
      private ServiceOfferingDao serviceOfferingDao;
      @Inject
      private VolumeDataFactory volumeDataFactory;
  
      private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
          List<SnapshotDataStoreVO> snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
          for (SnapshotDataStoreVO ref : snaps) {
              if (zoneId == dataStoreManager.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
                  return ref;
              }
          }
          return null;
      }
  
      @Override
      public Map<String, String> getCapabilities() {
          Map<String, String> mapCapabilities = new HashMap<>();
          mapCapabilities.put(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE.toString(), Boolean.TRUE.toString());
          mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_TEMPLATE_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
          return mapCapabilities;
      }
  
      @Override
      public DataTO getTO(DataObject data) {
          return null;
      }
  
      @Override
      public DataStoreTO getStoreTO(DataStore store) {
          return null;
      }
  
      @Override
      public long getUsedBytes(StoragePool storagePool) {
          return 0;
      }
  
      @Override
      public long getUsedIops(StoragePool storagePool) {
          return 0;
      }
  
      @Override
      public boolean grantAccess(DataObject data, Host host, DataStore dataStore) {
          return false;
      }
  
      @Override
      public void revokeAccess(DataObject data, Host host, DataStore dataStore) {
          if (DataObjectType.VOLUME == data.getType()) {
              final VolumeVO volume = volumeDao.findById(data.getId());
              if (volume.getInstanceId() == null) {
                  StorPoolUtil.spLog("Removing tags from detached volume=%s", volume.toString());
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
                  StorPoolUtil.volumeRemoveTags(StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true), conn);
              }
          }
      }
  
      private void updateStoragePool(final long poolId, final long deltaUsedBytes) {
          StoragePoolVO storagePool = primaryStoreDao.findById(poolId);
          final long capacity = storagePool.getCapacityBytes();
          final long used = storagePool.getUsedBytes() + deltaUsedBytes;
  
          storagePool.setUsedBytes(used < 0 ? 0 : (used > capacity ? capacity : used));
          primaryStoreDao.update(poolId, storagePool);
      }
  
      private String getVMInstanceUUID(Long id) {
          return id != null ? vmInstanceDao.findById(id).getUuid() : null;
      }
  
      protected void _completeResponse(final CreateObjectAnswer answer, final String err, final AsyncCompletionCallback<CommandResult> callback)
      {
          final CreateCmdResult res = new CreateCmdResult(null, answer);
          res.setResult(err);
          callback.complete(res);
      }
  
      protected void completeResponse(final DataTO result, final AsyncCompletionCallback<CommandResult> callback)
      {
          _completeResponse(new CreateObjectAnswer(result), null, callback);
      }
  
      protected void completeResponse(final String err, final AsyncCompletionCallback<CommandResult> callback)
      {
          _completeResponse(new CreateObjectAnswer(err), err, callback);
      }
  
      @Override
      public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) {
          return dataObject.getSize();
      }
  
      @Override
      public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) {
          return 0;
      }
  
      @Override
      public ChapInfo getChapInfo(DataObject dataObject) {
          return null;
      }
  
      @Override
      public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
          String path = null;
          Answer answer;
          String tier = null;
          String template = null;
          if (data.getType() == DataObjectType.VOLUME) {
              try {
                  VolumeInfo vinfo = (VolumeInfo)data;
                  String name = vinfo.getUuid();
                  Long size = vinfo.getPassphraseId() == null ? vinfo.getSize() : vinfo.getSize() + 2097152;
                  Long vmId = vinfo.getInstanceId();
  
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
  
                  if (vinfo.getDiskOfferingId() != null) {
                      tier = getTierFromOfferingDetail(vinfo.getDiskOfferingId());
                      if (tier == null) {
                          template = getTemplateFromOfferingDetail(vinfo.getDiskOfferingId());
                      }
                  }
  
                  SpApiResponse resp = createStorPoolVolume(template, tier, vinfo, name, size, vmId, conn);
                  if (resp.getError() == null) {
                      String volumeName = StorPoolUtil.getNameFromResponse(resp, false);
                      path = StorPoolUtil.devPath(volumeName);
  
                      updateVolume(dataStore, path, vinfo);
  
                      if (vinfo.getPassphraseId() != null) {
                          VolumeObjectTO volume = updateVolumeObjectTO(vinfo, resp);
                          answer = createEncryptedVolume(dataStore, data, vinfo, size, volume, null, true);
                      } else {
                          answer = new Answer(null, true, null);
                      }
                      updateStoragePool(dataStore.getId(), size);
                      StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.createAsync volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s", volumeName, vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), vinfo.getpayload(), conn.getTemplateName());
                  } else {
                      answer = new Answer(null, false, String.format("Could not create StorPool volume %s. Error: %s", name, resp.getError()));
                  }
              } catch (Exception e) {
                  answer = new Answer(null, false, String.format("Could not create volume due to %s", e.getMessage()));
              }
          } else {
              answer = new Answer(null, false, String.format("Invalid object type \"%s\"  passed to createAsync", data.getType()));
          }
          CreateCmdResult res = new CreateCmdResult(path, answer);
          res.setResult(answer.getDetails());
          if (callback != null) {
              callback.complete(res);
          }
      }
  
      private SpApiResponse createStorPoolVolume(String template, String tier, VolumeInfo vinfo, String name, Long size,
              Long vmId, SpConnectionDesc conn) {
          SpApiResponse resp = new SpApiResponse();
          Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, getVMInstanceUUID(vmId), "volume", getVcPolicyTag(vmId), tier);
          if (vinfo.getDeviceId() != null) {
              tags.put("disk", vinfo.getDeviceId().toString());
          }
          if (template == null) {
              template = conn.getTemplateName();
          }
          StorPoolVolumeDef volume = new StorPoolVolumeDef(null, size, tags, null, vinfo.getMaxIops(), template, null, null, null);
          resp = StorPoolUtil.volumeCreate(volume, conn);
          return resp;
      }
  
      private void updateVolume(DataStore dataStore, String path, VolumeInfo vinfo) {
          VolumeVO volume = volumeDao.findById(vinfo.getId());
          volume.setPoolId(dataStore.getId());
          volume.setPath(path);
          volume.setPoolType(StoragePoolType.StorPool);
          volumeDao.update(volume.getId(), volume);
      }
  
      private StorPoolSetVolumeEncryptionAnswer createEncryptedVolume(DataStore dataStore, DataObject data, VolumeInfo vinfo, Long size, VolumeObjectTO volume, String parentName, boolean isDataDisk) {
          StorPoolSetVolumeEncryptionAnswer ans;
          EndPoint ep = null;
          if (parentName == null) {
              ep = selector.select(data, vinfo.getPassphraseId() != null);
          } else {
              Long clusterId = StorPoolHelper.findClusterIdByGlobalId(parentName, clusterDao);
              if (clusterId == null) {
                  ep = selector.select(data, vinfo.getPassphraseId() != null);
              } else {
                  List<HostVO> hosts = hostDao.findByClusterIdAndEncryptionSupport(clusterId);
                  ep = CollectionUtils.isNotEmpty(hosts) ? RemoteHostEndPoint.getHypervisorHostEndPoint(hosts.get(0)) : ep;
              }
          }
          if (ep == null) {
              ans = new StorPoolSetVolumeEncryptionAnswer(null, false, "Could not find a host with volume encryption");
          } else {
              StorPoolSetVolumeEncryptionCommand cmd = new StorPoolSetVolumeEncryptionCommand(volume, parentName, isDataDisk);
              ans = (StorPoolSetVolumeEncryptionAnswer) ep.sendMessage(cmd);
              if (ans.getResult()) {
                  updateStoragePool(dataStore.getId(), size);
              }
          }
          return ans;
      }
  
      @Override
      public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
          String path = null;
          String err = null;
  
          if (data.getType() == DataObjectType.VOLUME) {
              VolumeObject vol = (VolumeObject)data;
              path = vol.getPath();
  
              err = resizeVolume(data, path, vol);
          } else {
              err = String.format("Invalid object type \"%s\"  passed to resize", data.getType());
          }
  
          CreateCmdResult res = new CreateCmdResult(path, new Answer(null, err != null, err));
          res.setResult(err);
          callback.complete(res);
      }
  
      private String resizeVolume(DataObject data, String path, VolumeObject vol) {
          String err = null;
          ResizeVolumePayload payload = (ResizeVolumePayload)vol.getpayload();
          boolean needResize = vol.getSize() != payload.newSize;
  
          final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(path, true);
          final long oldSize = vol.getSize();
          Long oldMaxIops = vol.getMaxIops();
  
          try {
              SpConnectionDesc conn = StorPoolUtil.getSpConnection(data.getDataStore().getUuid(), data.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
  
              err = updateStorPoolVolume(vol, payload, conn);
              if (err == null && needResize) {
                  err = notifyQemuForTheNewSize(data, err, vol, payload);
              }
              if (err != null) {
                  // try restoring volume to its initial size
                  SpApiResponse response = StorPoolUtil.volumeUpdate(name, oldSize, true, oldMaxIops, conn);
                  if (response.getError() != null) {
                      logger.debug(String.format("Could not resize StorPool volume %s back to its original size. Error: %s", name, response.getError()));
                  }
              } else {
                  updateVolumeWithTheNewSize(vol, payload);
              }
          } catch (Exception e) {
              logger.debug("sending resize command failed", e);
              err = e.toString();
          }
          return err;
      }
  
      private void updateVolumeWithTheNewSize(VolumeObject vol, ResizeVolumePayload payload) {
          vol.setSize(payload.newSize);
          vol.update();
          if (payload.newMaxIops != null) {
              VolumeVO volume = volumeDao.findById(vol.getId());
              volume.setMaxIops(payload.newMaxIops);
              volumeDao.update(volume.getId(), volume);
          }
          updateStoragePool(vol.getPoolId(), payload.newSize - vol.getSize());
      }
  
      private String notifyQemuForTheNewSize(DataObject data, String err, VolumeObject vol, ResizeVolumePayload payload)
              throws StorageUnavailableException {
          StoragePool pool = (StoragePool)data.getDataStore();
  
          StorPoolResizeVolumeCommand resizeCmd = new StorPoolResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), payload.newSize, payload.shrinkOk,
                  payload.instanceName, payload.hosts == null ? false : true);
          ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, payload.hosts, resizeCmd);
  
          if (answer == null || !answer.getResult()) {
              err = answer != null ? answer.getDetails() : "return a null answer, resize failed for unknown reason";
          }
          return err;
      }
  
      private String updateStorPoolVolume(VolumeObject vol, ResizeVolumePayload payload, SpConnectionDesc conn) {
          String err = null;
          String name = StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true);
          Long newDiskOfferingId = payload.getNewDiskOfferingId();
          String tier = null;
          String template = null;
          if (newDiskOfferingId != null) {
              tier = getTierFromOfferingDetail(newDiskOfferingId);
              if (tier == null) {
                  template = getTemplateFromOfferingDetail(newDiskOfferingId);
              }
          }
          SpApiResponse resp = new SpApiResponse();
          if (tier != null || template != null) {
              resp = updateVolumeByStorPoolQoS(payload, conn, name, tier, template);
          } else {
              resp = updateVolumeByOffering(vol, payload, conn, name);
          }
          if (resp.getError() != null) {
              err = String.format("Could not resize StorPool volume %s. Error: %s", name, resp.getError());
          }
          return err;
      }
  
      private static SpApiResponse updateVolumeByStorPoolQoS(ResizeVolumePayload payload, SpConnectionDesc conn, String name, String tier, String template) {
          Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, null, null, tier);
          StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, tags, null, null, template, null, null,
                  payload.shrinkOk);
          return StorPoolUtil.volumeUpdate(spVolume, conn);
      }
  
      private static SpApiResponse updateVolumeByOffering(VolumeObject vol, ResizeVolumePayload payload, SpConnectionDesc conn, String name) {
          long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : payload.newMaxIops;
  
          StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, null, null, maxIops, null, null, null,
                  payload.shrinkOk);
          StorPoolUtil.spLog(
                  "StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s",
                  name, vol.getUuid(), vol.getSize(), payload.newSize, payload.shrinkOk, maxIops);
          return StorPoolUtil.volumeUpdate(spVolume, conn);
      }
  
      @Override
      public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
          String err = null;
          if (data.getType() == DataObjectType.VOLUME) {
              VolumeInfo vinfo = (VolumeInfo)data;
              String name = StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true);
              StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.deleteAsync delete volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s dataStore=%s", name, vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), vinfo.getpayload(), dataStore.getUuid());
              if (name == null) {
                  name = vinfo.getUuid();
              }
              try {
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
                  tryToSnapshotVolumeBeforeDelete(vinfo, dataStore, name, conn);
                  SpApiResponse resp = StorPoolUtil.volumeDelete(name, conn);
                  if (resp.getError() == null) {
                      updateStoragePool(dataStore.getId(), - vinfo.getSize());
                      VolumeDetailVO detail = volumeDetailsDao.findDetail(vinfo.getId(), StorPoolUtil.SP_PROVIDER_NAME);
                      if (detail != null) {
                          volumeDetailsDao.remove(detail.getId());
                      }
                  } else {
                      if (!resp.getError().getName().equalsIgnoreCase("objectDoesNotExist")) {
                          err = String.format("Could not delete StorPool volume %s. Error: %s", name, resp.getError());
                      }
                  }
              } catch (Exception e) {
                  err = String.format("Could not delete volume due to %s", e.getMessage());
              }
          } else if (data.getType() == DataObjectType.SNAPSHOT) {
              err = deleteSnapshot((SnapshotInfo) data, err);
          } else {
              err = String.format("Invalid DataObjectType \"%s\" passed to deleteAsync", data.getType());
          }
  
          if (err != null) {
              logger.error(err);
              StorPoolUtil.spLog(err);
          }
  
          CommandResult res = new CommandResult();
          res.setResult(err);
          callback.complete(res);
      }
  
      private String deleteSnapshot(SnapshotInfo data, String err) {
          SnapshotInfo snapshot = data;
          SpConnectionDesc conn = StorPoolUtil.getSpConnection(snapshot.getDataStore().getUuid(), snapshot.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
          String name = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshot.getPath(), true);
          SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn);
          if (resp.getError() != null) {
              err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError());
              StorPoolUtil.spLog(err);
          }
          return err;
      }
  
      private void tryToSnapshotVolumeBeforeDelete(VolumeInfo vinfo, DataStore dataStore, String name, SpConnectionDesc conn) {
          Integer deleteAfter = StorPoolConfigurationManager.DeleteAfterInterval.valueIn(dataStore.getId());
          if (deleteAfter != null && deleteAfter > 0 && vinfo.getPassphraseId() == null) {
              createTemporarySnapshot(vinfo, name, deleteAfter, conn);
          } else {
              StorPoolUtil.spLog("The volume [%s] is not marked to be snapshot. Check the global setting `storpool.delete.after.interval` or the volume is encrypted [%s]", name, deleteAfter, vinfo.getPassphraseId() != null);
          }
      }
  
      private void createTemporarySnapshot(VolumeInfo vinfo, String name, Integer deleteAfter, SpConnectionDesc conn) {
          Map<String, String> tags = new HashMap<>();
          tags.put("cs", StorPoolUtil.DELAY_DELETE);
          StorPoolSnapshotDef snapshot = new StorPoolSnapshotDef(name, deleteAfter, tags);
          StorPoolUtil.spLog("Creating backup snapshot before delete the volume [%s]", vinfo.getName());
          SpApiResponse snapshotResponse = StorPoolUtil.volumeSnapshot(snapshot, conn);
          if (snapshotResponse.getError() == null) {
              String snapshotName = StorPoolUtil.getSnapshotNameFromResponse(snapshotResponse, false, StorPoolUtil.GLOBAL_ID);
              String snapshotPath = StorPoolUtil.devPath(snapshotName);
              SnapshotVO snapshotVo = createSnapshotVo(vinfo, snapshotName);
              createSnapshotOnPrimaryVo(vinfo, snapshotVo, snapshotPath);
              SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(snapshotVo.getId(), StorPoolUtil.SP_DELAY_DELETE, "~" + snapshotName, true);
              snapshotDetailsDao.persist(snapshotDetails);
          }
      }
  
      private void createSnapshotOnPrimaryVo(VolumeInfo vinfo, SnapshotVO snapshotVo, String snapshotPath) {
          SnapshotDataStoreVO snapshotOnPrimaryVo = new SnapshotDataStoreVO();
          snapshotOnPrimaryVo.setSnapshotId(snapshotVo.getId());
          snapshotOnPrimaryVo.setDataStoreId(vinfo.getDataCenterId());
          snapshotOnPrimaryVo.setRole(vinfo.getDataStore().getRole());
          snapshotOnPrimaryVo.setVolumeId(vinfo.getId());
          snapshotOnPrimaryVo.setSize(vinfo.getSize());
          snapshotOnPrimaryVo.setPhysicalSize(vinfo.getSize());
          snapshotOnPrimaryVo.setInstallPath(snapshotPath);
          snapshotOnPrimaryVo.setState(ObjectInDataStoreStateMachine.State.Ready);
          snapshotDataStoreDao.persist(snapshotOnPrimaryVo);
      }
  
      private SnapshotVO createSnapshotVo(VolumeInfo vinfo, String snapshotName) {
          SnapshotVO snapshotVo = new SnapshotVO(vinfo.getDataCenterId(), vinfo.getAccountId(), vinfo.getDomainId(), vinfo.getId(),
                  vinfo.getDiskOfferingId(), snapshotName,
                  (short)Snapshot.Type.RECURRING.ordinal(), Snapshot.Type.RECURRING.name(),
                  vinfo.getSize(), vinfo.getMinIops(), vinfo.getMaxIops(), vinfo.getHypervisorType(), Snapshot.LocationType.PRIMARY);
          snapshotVo.setState(com.cloud.storage.Snapshot.State.BackedUp);
          snapshotVo = snapshotDao.persist(snapshotVo);
          return snapshotVo;
      }
  
      private void logDataObject(final String pref, DataObject data) {
          final DataStore dstore = data.getDataStore();
          String name = null;
          Long size = null;
  
          if (data.getType() == DataObjectType.VOLUME) {
              VolumeInfo vinfo = (VolumeInfo)data;
              name = vinfo.getName();
              size = vinfo.getSize();
          } else if (data.getType() == DataObjectType.SNAPSHOT) {
              SnapshotInfo sinfo = (SnapshotInfo)data;
              name = sinfo.getName();
              size = sinfo.getSize();
          } else if (data.getType() == DataObjectType.TEMPLATE) {
              TemplateInfo tinfo = (TemplateInfo)data;
              name = tinfo.getName();
              size = tinfo.getSize();
          }
  
          StorPoolUtil.spLog("%s: name=%s, size=%s, uuid=%s, type=%s, dstore=%s:%s:%s", pref, name, size, data.getUuid(), data.getType(), dstore.getUuid(), dstore.getName(), dstore.getRole());
      }
  
      @Override
      public boolean canCopy(DataObject srcData, DataObject dstData) {
          DataObjectType srcType = srcData.getType();
          DataObjectType dstType = dstData.getType();
          if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.VOLUME) {
              return true;
          } else if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.SNAPSHOT) {
              return true;
          } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.TEMPLATE) {
              return true;
          } else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.TEMPLATE) {
              return true;
          } else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.VOLUME) {
              return true;
          } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.VOLUME) {
              return true;
          }
          return false;
      }
  
      @Override
      public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCallback<CopyCommandResult> callback) {
          StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc:");
          logDataObject("SRC", srcData);
          logDataObject("DST", dstData);
  
          final DataObjectType srcType = srcData.getType();
          final DataObjectType dstType = dstData.getType();
          String err = null;
          Answer answer = null;
          StorageSubSystemCommand cmd = null;
  
          try {
              if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.VOLUME) {
                  SnapshotInfo sinfo = (SnapshotInfo)srcData;
                  VolumeInfo vinfo = (VolumeInfo)dstData;
                  final String volumeName = vinfo.getUuid();
                  final Long size = vinfo.getSize();
  
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
                  String snapshotName =  StorPoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo) srcData).getPath(), true);
  
                  StorPoolVolumeDef spVolume = createVolumeWithTags(sinfo, snapshotName, vinfo, volumeName, size, conn);
                  SpApiResponse resp = StorPoolUtil.volumeCreate(spVolume, conn);
                  if (resp.getError() == null) {
                      updateStoragePool(dstData.getDataStore().getId(), size);
  
                      VolumeObjectTO to = (VolumeObjectTO)dstData.getTO();
                      to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
                      to.setSize(size);
                      updateVolumePoolType(vinfo);
  
                      answer = new CopyCmdAnswer(to);
                      StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", StorPoolUtil.getNameFromResponse(resp, false), volumeName, snapshotName, sinfo.getUuid());
                  } else if (resp.getError().getName().equals("objectDoesNotExist")) {
                      //check if snapshot is on secondary storage
                      StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snapshot on secondary storage", snapshotName);
                      SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId());
                      SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE);
                      if (snapshotDetail != null) {
                          answer = new Answer(cmd, false, String.format("Could not create volume from snapshot due to: %s. The snapshot was created with the delayDelete option.", resp.getError()));
                      } else if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) {
                          spVolume.setParent(null);
                          SpApiResponse emptyVolumeCreateResp = StorPoolUtil.volumeCreate(spVolume, conn);
                          if (emptyVolumeCreateResp.getError() == null) {
                              answer = createVolumeFromSnapshot(srcData, dstData, size, emptyVolumeCreateResp);
                          } else {
                              answer = new Answer(cmd, false, String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, emptyVolumeCreateResp.getError()));
                          }
                          VolumeObjectTO to = (VolumeObjectTO) dstData.getTO();
                          to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
                          to.setSize(size);
  
                          answer = new CopyCmdAnswer(to);
                          StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", StorPoolUtil.getNameFromResponse(resp, false), to.getUuid(), snapshotName, sinfo.getUuid());
                      } else {
                          err = String.format("Could not create volume from a snapshot due to {}", resp.getError());
                      }
                  } else if (sinfo.getDataStore().getRole().equals(DataStoreRole.Image)) {
                      //check if snapshot is on secondary storage
                      StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snapshot on secondary storage", sinfo.getName());
                      SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId());
                      SpApiResponse emptyVolumeCreateResp = StorPoolUtil.volumeCreate(volumeName, null, size, null, null, "volume", null, conn);
                      if (emptyVolumeCreateResp.getError() == null) {
                          answer = createVolumeFromSnapshot(srcData, dstData, size, emptyVolumeCreateResp);
                      } else {
                          answer = new Answer(cmd, false, String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, emptyVolumeCreateResp.getError()));
                      }
                  } else {
                      answer = new Answer(cmd, false, String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError()));
                  }
              } else if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.SNAPSHOT) {
                  SnapshotInfo sinfo = (SnapshotInfo)srcData;
                  SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE);
                  // bypass secondary storage
                  if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) {
                      SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData.getTO();
                      answer = new CopyCmdAnswer(snapshot);
                  } else {
                      // copy snapshot to secondary storage (backup snapshot)
                      cmd = new StorPoolBackupSnapshotCommand(srcData.getTO(), dstData.getTO(), StorPoolHelper.getTimeout(StorPoolHelper.BackupSnapshotWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
  
                      final String snapName =  StorPoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo) srcData).getPath(), true);
                      SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
                      try {
                          Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(snapName, conn), clusterDao);
                          HostVO host = clusterId != null ? StorPoolHelper.findHostByCluster(clusterId, hostDao) : null;
                          EndPoint ep = host != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(host) : selector.select(srcData, dstData);                        if (ep == null) {
                              err = "No remote endpoint to send command, check if host or ssvm is down?";
                          } else {
                              answer = ep.sendMessage(cmd);
                              // if error during snapshot backup, cleanup the StorPool snapshot
                              if (answer != null && !answer.getResult()) {
                                  StorPoolUtil.spLog(String.format("Error while backing-up snapshot '%s' - cleaning up StorPool snapshot. Error: %s", snapName, answer.getDetails()));
                                  SpApiResponse resp = StorPoolUtil.snapshotDelete(snapName, conn);
                                  if (resp.getError() != null) {
                                      final String err2 = String.format("Failed to cleanup StorPool snapshot '%s'. Error: %s.", snapName, resp.getError());
                                      logger.error(err2);
                                      StorPoolUtil.spLog(err2);
                                  }
                              }
                          }
                      } catch (CloudRuntimeException e) {
                          err = e.getMessage();
                      }
                  }
              } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.TEMPLATE) {
                  // create template from volume
                  VolumeObjectTO volume = (VolumeObjectTO) srcData.getTO();
                  TemplateObjectTO template = (TemplateObjectTO) dstData.getTO();
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
  
                  String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
  
  
                  cmd = new StorPoolBackupTemplateFromSnapshotCommand(volume, template,
                          StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
  
                  try {
                      EndPoint ep2 = selector.select(srcData, dstData);
                      if (ep2 == null) {
                          err = "No remote endpoint to send command, check if host or ssvm is down?";
                      } else {
                          answer = ep2.sendMessage(cmd);
                          if (answer != null && answer.getResult()) {
                              SpApiResponse resSnapshot = StorPoolUtil.volumeSnapshot(volumeName, template.getUuid(), null, "template", "no", conn);
                              if (resSnapshot.getError() != null) {
                                  logger.debug(String.format("Could not snapshot volume with ID=%s", volume.getId()));
                                  StorPoolUtil.spLog("Volume snapshot failed with error=%s", resSnapshot.getError().getDescr());
                                  err = resSnapshot.getError().getDescr();
                              }
                              else {
                                  StorPoolHelper.updateVmStoreTemplate(template.getId(), template.getDataStore().getRole(), StorPoolUtil.devPath(StorPoolUtil.getSnapshotNameFromResponse(resSnapshot, false, StorPoolUtil.GLOBAL_ID)), vmTemplateDataStoreDao);
                                  vmTemplateDetailsDao.persist(new VMTemplateDetailVO(template.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, String.valueOf(srcData.getDataStore().getId()), false));
                              }
                          }else {
                              err = "Could not copy template to secondary " + answer.getResult();
                          }
                      }
                  }catch (CloudRuntimeException e) {
                      err = e.getMessage();
                  }
              } else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.TEMPLATE) {
                  // copy template to primary storage
                  TemplateInfo tinfo = (TemplateInfo)dstData;
                  Long size = tinfo.getSize();
                  if(size == null || size == 0)
                      size = 1L*1024*1024*1024;
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
  
                  TemplateDataStoreVO templDataStoreVO = vmTemplateDataStoreDao.findByTemplate(tinfo.getId(), DataStoreRole.Image);
  
                  String snapshotName = (templDataStoreVO != null && templDataStoreVO.getLocalDownloadPath() != null)
                          ? StorPoolStorageAdaptor.getVolumeNameFromPath(templDataStoreVO.getLocalDownloadPath(), true)
                          : null;
                  String name = tinfo.getUuid();
  
                  SpApiResponse resp = null;
                  if (snapshotName != null) {
                      //no need to copy volume from secondary, because we have it already on primary. Just need to create a child snapshot from it.
                      //The child snapshot is needed when configuration "storage.cleanup.enabled" is true, not to clean the base snapshot and to lose everything
                      resp = StorPoolUtil.volumeCreate(name, snapshotName, size, null, "no", "template", null, conn);
                      if (resp.getError() != null) {
                          err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
                      } else {
                          String volumeNameToSnapshot = StorPoolUtil.getNameFromResponse(resp, true);
                          TemplateObjectTO dstTO = (TemplateObjectTO) dstData.getTO();
  
                          answer = createVolumeSnapshot(cmd, size, conn, volumeNameToSnapshot, dstTO);
                          StorPoolUtil.volumeDelete(volumeNameToSnapshot, conn);
                      }
                  } else {
                      resp = StorPoolUtil.volumeCreate(name, null, size, null, "no", "template", null, conn);
                      if (resp.getError() != null) {
                          err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
                      } else {
                          String volName = StorPoolUtil.getNameFromResponse(resp, true);
                          TemplateObjectTO dstTO = (TemplateObjectTO)dstData.getTO();
                          dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
                          dstTO.setSize(size);
  
                          cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume");
  
                          EndPoint ep = selector.select(srcData, dstData);
                          if (ep == null) {
                              err = "No remote endpoint to send command, check if host or ssvm is down?";
                          } else {
                              answer = ep.sendMessage(cmd);
                          }
  
                          if (answer != null && answer.getResult()) {
                              // successfully downloaded template to primary storage
                              TemplateObjectTO templ = (TemplateObjectTO) ((CopyCmdAnswer) answer).getNewData();
                              answer = createVolumeSnapshot(cmd, size, conn, volName, templ);
                          } else {
                              err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.";
                          }
  
                          StorPoolUtil.volumeDelete(volName, conn);
                      }
                  }
              } else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.VOLUME) {
                  // create volume from template on Storpool PRIMARY
                  TemplateInfo tinfo = (TemplateInfo)srcData;
  
                  VolumeInfo vinfo = (VolumeInfo) dstData;
                  VMTemplateStoragePoolVO templStoragePoolVO = StorPoolHelper.findByPoolTemplate(vinfo.getPoolId(),
                          tinfo.getId());
                  final String parentName = templStoragePoolVO.getLocalDownloadPath() != null
                          ? StorPoolStorageAdaptor.getVolumeNameFromPath(templStoragePoolVO.getLocalDownloadPath(), true)
                          : StorPoolStorageAdaptor.getVolumeNameFromPath(templStoragePoolVO.getInstallPath(), true);
                  final String name = vinfo.getUuid();
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(),
                          vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
  
                  Long snapshotSize = templStoragePoolVO.getTemplateSize();
                  boolean withoutEncryption = vinfo.getPassphraseId() == null;
                  long size = withoutEncryption ? vinfo.getSize() : vinfo.getSize() + 2097152;
                  if (snapshotSize != null && size < snapshotSize) {
                      StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize));
                      size = withoutEncryption ? snapshotSize : snapshotSize + 2097152;
                  }
                  StorPoolUtil.spLog(String.format("volume size is: %d", size));
                  Long vmId = vinfo.getInstanceId();
  
                  String template = null;
                  String tier = null;
                  SpApiResponse resp = new SpApiResponse();
  
                  if (vinfo.getDiskOfferingId() != null) {
                      tier = getTierFromOfferingDetail(vinfo.getDiskOfferingId());
                      if (tier == null) {
                          template = getTemplateFromOfferingDetail(vinfo.getDiskOfferingId());
                      }
                      StorPoolUtil.spLog(
                              "Creating volume [%s] with template [%s] or tier tags [%s] described in disk/service offerings details",
                              vinfo.getUuid(), template, tier);
                  }
  
                  Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, getVMInstanceUUID(vmId), "volume", getVcPolicyTag(vmId), tier);
  
                  if (vinfo.getDeviceId() != null) {
                      tags.put("disk", vinfo.getDeviceId().toString());
                  }
  
                  if (template == null) {
                      template = conn.getTemplateName();
                  }
  
                  StorPoolVolumeDef volumeDef = new StorPoolVolumeDef(null, size, tags, parentName, null, template, null, null, null);
                  resp = StorPoolUtil.volumeCreate(volumeDef, conn);
  
                  if (resp.getError() == null) {
                      updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize());
                      updateVolumePoolType(vinfo);
  
                      if (withoutEncryption) {
                          VolumeObjectTO to = updateVolumeObjectTO(vinfo, resp);
                          answer = new CopyCmdAnswer(to);
                      } else {
                          VolumeObjectTO volume = updateVolumeObjectTO(vinfo, resp);
                          String snapshotPath = StorPoolUtil.devPath(parentName.split("~")[1]);
                          answer = createEncryptedVolume(dstData.getDataStore(), dstData, vinfo, size, volume, snapshotPath, false);
                          if (answer.getResult()) {
                               answer = new CopyCmdAnswer(((StorPoolSetVolumeEncryptionAnswer) answer).getVolume());
                          }
                      }
                  } else {
                      err = String.format("Could not create Storpool volume %s. Error: %s", name, resp.getError());
                  }
              } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.VOLUME) {
                  StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.copyAsync src Data Store=%s", srcData.getDataStore().getDriver());
                  VolumeInfo dstInfo = (VolumeInfo)dstData;
                  VolumeInfo srcInfo = (VolumeInfo) srcData;
  
                  if( !(srcData.getDataStore().getDriver() instanceof StorPoolPrimaryDataStoreDriver ) ) {
                      // copy "VOLUME" to primary storage
                      String name = dstInfo.getUuid();
                      Long size = dstInfo.getSize();
                      if(size == null || size == 0)
                          size = 1L*1024*1024*1024;
                      SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
                      Long vmId = srcInfo.getInstanceId();
  
                      SpApiResponse resp = StorPoolUtil.volumeCreate(name, null, size, getVMInstanceUUID(vmId), getVcPolicyTag(vmId), "volume", dstInfo.getMaxIops(), conn);
                      if (resp.getError() != null) {
                          err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
                      } else {
                          //updateVolume(dstData.getId());
                          VolumeObjectTO dstTO = (VolumeObjectTO)dstData.getTO();
                          dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
                          dstTO.setSize(size);
  
                          cmd = new StorPoolDownloadVolumeCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
  
                          EndPoint ep = selector.select(srcData, dstData);
  
                          if (ep == null || storagePoolHostDao.findByPoolHost(dstData.getId(), ep.getId()) == null) {
                              StorPoolUtil.spLog("select(srcData, dstData) returned NULL or the destination pool is not connected to the selected host. Trying dstData");
                              ep = selector.select(dstData); // Storpool is zone
                          }
                          if (ep == null) {
                              err = "No remote endpoint to send command, check if host or ssvm is down?";
                          } else {
                              StorPoolUtil.spLog("Sending command to %s", ep.getHostAddr());
                              answer = ep.sendMessage(cmd);
  
                              if (answer != null && answer.getResult()) {
                                  // successfully downloaded volume to primary storage
                              } else {
                                  err = answer != null ? answer.getDetails() : "Unknown error while downloading volume. Null answer returned.";
                              }
                          }
  
                          if (err != null) {
                              SpApiResponse resp3 = StorPoolUtil.volumeDelete(name, conn);
                              if (resp3.getError() != null) {
                                 logger.warn(String.format("Could not clean-up Storpool volume %s. Error: %s", name, resp3.getError()));
                              }
                          }
                      }
                  } else {
                      // download volume - first copies to secondary
                      VolumeObjectTO srcTO = (VolumeObjectTO)srcData.getTO();
                      StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc SRC path=%s DST canonicalName=%s ", srcTO.getPath(), dstData.getDataStore().getClass().getCanonicalName());
                      PrimaryDataStoreTO checkStoragePool = dstData.getTO().getDataStore() instanceof PrimaryDataStoreTO ? (PrimaryDataStoreTO)dstData.getTO().getDataStore() : null;
                      final String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
  
                      if (checkStoragePool != null && checkStoragePool.getPoolType().equals(StoragePoolType.StorPool)) {
                          answer = migrateVolumeToStorPool(srcData, dstData, srcInfo, srcTO, volumeName);
                      } else {
                          SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
                          final SpApiResponse resp = StorPoolUtil.volumeSnapshot(volumeName, srcTO.getUuid(), srcInfo.getInstanceId() != null ? getVMInstanceUUID(srcInfo.getInstanceId()) : null, "temporary", null, conn);
                          String snapshotName = StorPoolUtil.getSnapshotNameFromResponse(resp, true, StorPoolUtil.GLOBAL_ID);
                          if (resp.getError() == null) {
                              srcTO.setPath(StorPoolUtil.devPath(
                                      StorPoolUtil.getSnapshotNameFromResponse(resp, false, StorPoolUtil.GLOBAL_ID)));
  
                              cmd = new StorPoolCopyVolumeToSecondaryCommand(srcTO, dstData.getTO(), StorPoolHelper.getTimeout(StorPoolHelper.CopyVolumeWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
  
                              StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc command=%s ", cmd);
  
                              try {
                                  Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(snapshotName, conn), clusterDao);
                                  HostVO host = clusterId != null ? StorPoolHelper.findHostByCluster(clusterId, hostDao) : null;
                                  EndPoint ep = host != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(host) : selector.select(srcData, dstData);
                                  StorPoolUtil.spLog("selector.select(srcData, dstData) ", ep);
                                  if (ep == null) {
                                      ep = selector.select(dstData);
                                      StorPoolUtil.spLog("selector.select(srcData) ", ep);
                                  }
  
                                  if (ep == null) {
                                      err = "No remote endpoint to send command, check if host or ssvm is down?";
                                  } else {
                                      answer = ep.sendMessage(cmd);
                                      StorPoolUtil.spLog("Answer: details=%s, result=%s", answer.getDetails(), answer.getResult());
                                  }
                              } catch (CloudRuntimeException e) {
                                  err = e.getMessage();
                              }
                          } else {
                              err = String.format("Failed to create temporary StorPool snapshot while trying to download volume %s (uuid %s). Error: %s", srcTO.getName(), srcTO.getUuid(), resp.getError());
                          }
                          final SpApiResponse resp2 = StorPoolUtil.snapshotDelete(snapshotName, conn);
                          if (resp2.getError() != null) {
                              final String err2 = String.format("Failed to delete temporary StorPool snapshot %s. Error: %s", StorPoolUtil.getNameFromResponse(resp, true), resp2.getError());
                              logger.error(err2);
                              StorPoolUtil.spLog(err2);
                          }
                      }
                  }
              } else {
                  err = String.format("Unsupported copy operation from %s (type %s) to %s (type %s)", srcData.getUuid(), srcType, dstData.getUuid(), dstType);
              }
          } catch (Exception e) {
              StorPoolUtil.spLog("Caught exception: %s", e.toString());
              err = e.toString();
          }
  
          if (answer != null && !answer.getResult()) {
              err = answer.getDetails();
          }
  
          if (err != null) {
              StorPoolUtil.spLog("Failed due to %s", err);
  
              logger.error(err);
              answer = new Answer(cmd, false, err);
          }
  
          CopyCommandResult res = new CopyCommandResult(null, answer);
          res.setResult(err);
          callback.complete(res);
      }
  
      private StorPoolVolumeDef createVolumeWithTags(SnapshotInfo sinfo, String snapshotName, VolumeInfo vinfo, String volumeName, Long size, SpConnectionDesc conn) {
          Pair<String, String> templateAndTier = getTemplateAndTier(vinfo, conn);
          Map<String, String> tags = StorPoolHelper.addStorPoolTags(volumeName, getVMInstanceUUID(vinfo.getInstanceId()), "volume", getVcPolicyTag(vinfo.getInstanceId()), templateAndTier.first());
          return new StorPoolVolumeDef(null, size, tags, snapshotName, sinfo.getBaseVolume().getMaxIops(), templateAndTier.second(), null, null, null);
      }
  
      private Pair<String, String> getTemplateAndTier(VolumeInfo vinfo, SpConnectionDesc conn) {
          String tier = null;
          String template = null;
          if (vinfo.getDiskOfferingId() != null) {
              tier = getTierFromOfferingDetail(vinfo.getDiskOfferingId());
              if (tier == null) {
                  template = getTemplateFromOfferingDetail(vinfo.getDiskOfferingId());
              }
          }
  
          if (template == null) {
              template = conn.getTemplateName();
          }
          return new Pair<>(tier, template);
      }
      private Answer createVolumeSnapshot(StorageSubSystemCommand cmd, Long size, SpConnectionDesc conn,
              String volName, TemplateObjectTO dstTO) {
          Answer answer;
          SpApiResponse resp = StorPoolUtil.volumeSnapshot(volName, dstTO.getUuid(), null, "template", null, conn);
          if (resp.getError() != null) {
              answer = new Answer(cmd, false, String.format("Could not snapshot volume. Error: %s", resp.getError()));
          } else {
              dstTO.setPath(StorPoolUtil.devPath(
                      StorPoolUtil.getSnapshotNameFromResponse(resp, false, StorPoolUtil.GLOBAL_ID)));
              answer = new CopyCmdAnswer(dstTO);
          }
          return answer;
      }
  
      private Answer createVolumeFromSnapshot(DataObject srcData, DataObject dstData, final Long size,
              SpApiResponse emptyVolumeCreateResp) {
          Answer answer;
          String name = StorPoolUtil.getNameFromResponse(emptyVolumeCreateResp, false);
          VolumeObjectTO dstTO = (VolumeObjectTO) dstData.getTO();
          dstTO.setSize(size);
          dstTO.setPath(StorPoolUtil.devPath(name));
          StorageSubSystemCommand cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume");
  
          EndPoint ep = selector.select(srcData, dstData);
          if (ep == null) {
              answer = new Answer(cmd, false, "\"No remote endpoint to send command, check if host or ssvm is down?\"");
          } else {
              answer = ep.sendMessage(cmd);
          }
          if (answer == null || !answer.getResult()) {
              answer = new Answer(cmd, false, answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.");
          }
          return answer;
      }
  
      private void updateVolumePoolType(VolumeInfo vinfo) {
          VolumeVO volumeVO = volumeDao.findById(vinfo.getId());
          volumeVO.setPoolType(StoragePoolType.StorPool);
          volumeDao.update(volumeVO.getId(), volumeVO);
      }
  
      private VolumeObjectTO updateVolumeObjectTO(VolumeInfo vinfo, SpApiResponse resp) {
          VolumeObjectTO to = (VolumeObjectTO) vinfo.getTO();
          to.setSize(vinfo.getSize());
          to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
          return to;
      }
  
      /**
       * Live migrate/copy volume from one StorPool storage to another
       * @param srcData The source volume data
       * @param dstData The destination volume data
       * @param srcInfo The source volume info
       * @param srcTO The source Volume TO
       * @param volumeName The name of the volume
       * @return Answer
       */
      private Answer migrateVolumeToStorPool(DataObject srcData, DataObject dstData, VolumeInfo srcInfo,
              VolumeObjectTO srcTO, final String volumeName) {
          Answer answer;
          SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
          String baseOn = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
  
          String vmUuid = null;
          String vcPolicyTag = null;
  
          VMInstanceVO vm = null;
          if (srcInfo.getInstanceId() != null) {
              vm = vmInstanceDao.findById(srcInfo.getInstanceId());
          }
  
          if (vm != null) {
              vmUuid = vm.getUuid();
              vcPolicyTag = getVcPolicyTag(vm.getId());
          }
  
          if (vm != null && vm.getState().equals(State.Running)) {
              answer = migrateVolume(srcData, dstData, volumeName, conn);
          } else {
              answer = copyVolume(srcInfo, srcTO, conn, baseOn, vmUuid, vcPolicyTag);
          }
          return answer;
      }
  
      /**
       * Copy the volume from StorPool primary storage to another StorPool primary storage
       * @param srcInfo The source volume info
       * @param srcTO The source Volume TO
       * @param conn StorPool connection
       * @param baseOn The name of an already existing volume that the new volume is to be a copy of.
       * @param vmUuid The UUID of the VM
       * @param vcPolicyTag The VC policy tag
       * @return Answer
       */
      private Answer copyVolume(VolumeInfo srcInfo, VolumeObjectTO srcTO, SpConnectionDesc conn, String baseOn, String vmUuid, String vcPolicyTag) {
          //uuid tag will be the same as srcData.uuid
          String volumeName = srcInfo.getUuid();
          Long iops = (srcInfo.getMaxIops() != null && srcInfo.getMaxIops().longValue() > 0) ? srcInfo.getMaxIops() : null;
          SpApiResponse response = StorPoolUtil.volumeCopy(volumeName, baseOn, "volume", iops, vmUuid, vcPolicyTag, conn);
          if (response.getError() != null) {
              return new CopyCmdAnswer(String.format("Could not copy volume [%s] due to %s", baseOn, response.getError()));
          }
          String newVolume = StorPoolUtil.getNameFromResponse(response, false);
  
          StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc copy volume[%s] from pool[%s] with a new name [%s]",
                  baseOn, srcInfo.getDataStore().getName(), newVolume);
  
          srcTO.setSize(srcInfo.getSize());
          srcTO.setPath(StorPoolUtil.devPath(newVolume));
  
          return new CopyCmdAnswer(srcTO);
      }
  
      /**
       * Live migrate the StorPool's volume to another StorPool template
       * @param srcData The source data volume
       * @param dstData The destination data volume
       * @param name The volume's name
       * @param conn StorPool's connection
       * @return Answer
       */
      private Answer migrateVolume(DataObject srcData, DataObject dstData, String name, SpConnectionDesc conn) {
          Answer answer;
          SpApiResponse resp = StorPoolUtil.volumeUpdateTemplate(name, conn);
          if (resp.getError() != null) {
              answer = new Answer(null, false, String.format("Could not migrate volume %s to %s due to %s", name, conn.getTemplateName(), resp.getError()));
          } else {
              StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc migrate volume[%s] from pool[%s] to pool[%s]",
                      name, srcData.getDataStore().getName(),dstData.getDataStore().getName());
              VolumeVO updatedVolume = volumeDao.findById(srcData.getId());
              updatedVolume.setPoolId(dstData.getDataStore().getId());
              updatedVolume.setLastPoolId(srcData.getDataStore().getId());
              volumeDao.update(updatedVolume.getId(), updatedVolume);
              answer = new Answer(null, true, null);
          }
          return answer;
      }
  
      @Override
      public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
          String snapshotName = snapshot.getUuid();
          VolumeInfo vinfo = snapshot.getBaseVolume();
          String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true);
          Long vmId = vinfo.getInstanceId();
          if (volumeName != null) {
              StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.takeSnapshot volumename=%s vmInstance=%s",volumeName, vmId);
          } else {
              throw new UnsupportedOperationException("The path should be: " + StorPoolUtil.SP_DEV_PATH);
          }
  
          CreateObjectAnswer answer = null;
          String err = null;
  
          try {
              SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
  
              SpApiResponse resp = StorPoolUtil.volumeSnapshot(volumeName, snapshotName, vmId != null ? getVMInstanceUUID(vmId) : null, "snapshot", null, conn);
  
              if (resp.getError() != null) {
                  err = String.format("Could not snapshot StorPool volume %s. Error %s", volumeName, resp.getError());
                  answer = new CreateObjectAnswer(err);
              } else {
                  String name = StorPoolUtil.getSnapshotNameFromResponse(resp, true, StorPoolUtil.GLOBAL_ID);
                  SnapshotObjectTO snapTo = (SnapshotObjectTO)snapshot.getTO();
                  snapTo.setPath(StorPoolUtil.devPath(name.split("~")[1]));
                  answer = new CreateObjectAnswer(snapTo);
                  StorPoolHelper.addSnapshotDetails(snapshot.getId(), snapshot.getUuid(), snapTo.getPath(), snapshotDetailsDao);
                  //add primary storage of snapshot
                  StorPoolHelper.addSnapshotDetails(snapshot.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, String.valueOf(snapshot.getDataStore().getId()), snapshotDetailsDao);
                  StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.takeSnapshot: snapshot: name=%s, uuid=%s, volume: name=%s, uuid=%s", name, snapshot.getUuid(), volumeName, vinfo.getUuid());
              }
          } catch (Exception e) {
              err = String.format("Could not take volume snapshot due to %s", e.getMessage());
          }
  
          CreateCmdResult res = new CreateCmdResult(null, answer);
          res.setResult(err);
          callback.complete(res);
      }
  
      @Override
      public void revertSnapshot(final SnapshotInfo snapshot, final SnapshotInfo snapshotOnPrimaryStore, final AsyncCompletionCallback<CommandResult> callback) {
          final VolumeInfo vinfo = snapshot.getBaseVolume();
          final String snapshotName = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), snapshotDataStoreDao, snapshotDetailsDao);
          final String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true);
          StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.revertSnapshot: snapshot: name=%s, uuid=%s, volume: name=%s, uuid=%s", snapshotName, snapshot.getUuid(), volumeName, vinfo.getUuid());
          String err = null;
  
          SpConnectionDesc conn = null;
          try {
              conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
          } catch (Exception e) {
              err = String.format("Could not revert volume due to %s", e.getMessage());
              completeResponse(err, callback);
              return;
          }
  
          VolumeDetailVO detail = volumeDetailsDao.findDetail(vinfo.getId(), StorPoolUtil.SP_PROVIDER_NAME);
          if (detail != null) {
              //Rename volume to its global id only if it was migrated from UUID to global id
              SpApiResponse updateVolumeResponse = StorPoolUtil.volumeUpdateRename(StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true), "", StorPoolStorageAdaptor.getVolumeNameFromPath(detail.getValue(), false), conn);
  
              if (updateVolumeResponse.getError() != null) {
                  StorPoolUtil.spLog("Could not update StorPool's volume %s to it's globalId due to %s", StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true), updateVolumeResponse.getError().getDescr());
                  err = String.format("Could not update StorPool's volume %s to it's globalId due to %s", StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true), updateVolumeResponse.getError().getDescr());
                  completeResponse(err, callback);
                  return;
              }
              volumeDetailsDao.remove(detail.getId());
          }
  
          SpApiResponse resp = StorPoolUtil.detachAllForced(volumeName, false, conn);
          if (resp.getError() != null) {
              err = String.format("Could not detach StorPool volume %s due to %s", volumeName, resp.getError());
              completeResponse(err, callback);
              return;
          }
          SpApiResponse response = StorPoolUtil.volumeRevert(volumeName, snapshotName, conn);
          if (response.getError() != null) {
              err = String.format(
                      "Could not revert StorPool volume %s to the %s snapshot: could not create the new volume: error %s",
                      volumeName, snapshotName, response.getError());
              completeResponse(err, callback);
              return;
          }
  
          if (vinfo.getMaxIops() != null) {
              response = StorPoolUtil.volumeUpdateIopsAndTags(volumeName, null, vinfo.getMaxIops(), conn, null);
              if (response.getError() != null) {
                  StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s", response.getError().getDescr());
              }
          }
  
          final VolumeObjectTO to = (VolumeObjectTO)vinfo.getTO();
          completeResponse(to, callback);
      }
  
      private String getVcPolicyTag(Long vmId) {
          ResourceTag resourceTag = vmId != null ? resourceTagDao.findByKey(vmId, ResourceObjectType.UserVm, StorPoolUtil.SP_VC_POLICY) : null;
          return resourceTag != null ? resourceTag.getValue() : "";
      }
  
      public void handleQualityOfServiceForVolumeMigration(VolumeInfo arg0, QualityOfServiceState arg1) {
          logger.debug(String.format("handleQualityOfServiceForVolumeMigration with volume name=%s is not supported", arg0.getName()));
      }
  
  
      public void copyAsync(DataObject srcData, DataObject destData, Host destHost,
              AsyncCompletionCallback<CopyCommandResult> callback) {
          copyAsync(srcData, destData, callback);
      }
  
      public boolean canProvideStorageStats() {
          return StorPoolConfigurationManager.StorageStatsInterval.value() > 0;
      }
  
      public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
          if (storagePool == null) {
              return null;
          }
          Map<Long, Map<String, Pair<Long, Long>>> templatesStats = StorPoolStatsCollector.templatesStats;
          if (MapUtils.isNotEmpty(templatesStats) && templatesStats.containsKey(storagePool.getDataCenterId())) {
              Map<String, Pair<Long, Long>> storageStats = templatesStats.get(storagePool.getDataCenterId());
              StoragePoolDetailVO templateName = storagePoolDetailsDao.findDetail(storagePool.getId(), StorPoolUtil.SP_TEMPLATE);
              if (storageStats.containsKey(templateName.getValue()) && templateName != null) {
                  Pair<Long, Long> stats = storageStats.get(templateName.getValue());
                  if (stats.first() != storagePool.getCapacityBytes()) {
                      primaryStoreDao.updateCapacityBytes(storagePool.getId(), stats.first());
                  }
                  return storageStats.get(templateName.getValue());
              }
          }
          return null;
      }
  
      public boolean canProvideVolumeStats() {
          return StorPoolConfigurationManager.VolumesStatsInterval.value() > 0;
      }
  
      public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
  
          if (volumeId == null) {
              return null;
          }
  
          Map<String, Pair<Long, Long>> volumesStats = StorPoolStatsCollector.volumesStats;
          if (MapUtils.isNotEmpty(volumesStats)) {
              Pair<Long, Long> volumeStats = volumesStats.get(StorPoolStorageAdaptor.getVolumeNameFromPath(volumeId, true));
              if (volumeStats != null) {
                  return volumeStats;
              }
          } else {
              List<VolumeVO> volumes = volumeDao.findByPoolId(storagePool.getId());
              for (VolumeVO volume : volumes) {
                  if (volume.getPath() != null && volume.getPath().equals(volumeId)) {
                      long size = volume.getSize();
                      StorPoolUtil.spLog("Volume [%s] doesn't have any statistics, returning its size [%s]", volumeId, size);
                      return new Pair<>(size, size);
                  }
              }
          }
          return null;
      }
  
      public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
          return false;
      }
  
      @Override
      public boolean isVmInfoNeeded() {
          return true;
      }
  
      @Override
      public void provideVmInfo(long vmId, long volumeId) {
          VolumeVO volume = volumeDao.findById(volumeId);
          if (volume.getInstanceId() == null) {
              return;
          }
          StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
          if (poolVO != null && StoragePoolType.StorPool.equals(poolVO.getPoolType())) {
              VolumeInfo vInfo = volumeDataFactory.getVolume(volumeId);
              if (vInfo == null) {
                  StorPoolUtil.spLog("Could not find volume with volume ID [%s] to set tags", volumeId);
                  return;
              }
              updateVolumeWithTags(poolVO, vInfo);
          }
      }
  
      private void updateVolumeWithTags(StoragePoolVO poolVO, VolumeInfo vInfo) {
          try {
              SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
              String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(vInfo.getPath(), true);
              Pair<String, String> templateAndTier = getTemplateAndTier(vInfo, conn);
              Map<String, String> tags = StorPoolHelper.addStorPoolTags(volName, getVMInstanceUUID(vInfo.getInstanceId()), "volume", getVcPolicyTag(vInfo.getInstanceId()), templateAndTier.first());
              if (vInfo.getDeviceId() != null) {
                  tags.put("disk", vInfo.getDeviceId().toString());
              }
              StorPoolVolumeDef spVolume = new StorPoolVolumeDef(volName, null, tags, null, null, templateAndTier.second(), null, null, null);
              StorPoolUtil.spLog("Updating volume's tags [%s] with template [%s]", tags, templateAndTier.second());
              SpApiResponse resp = StorPoolUtil.volumeUpdate(spVolume, conn);
              if (resp.getError() != null) {
                  logger.warn(String.format("Could not update VC policy tags of a volume with id [%s]", vInfo.getUuid()));
              }
          } catch (Exception e) {
              logger.warn(String.format("Could not update Virtual machine tags due to %s", e.getMessage()));
          }
      }
  
      @Override
      public boolean isVmTagsNeeded(String tagKey) {
          return tagKey != null && tagKey.equals(StorPoolUtil.SP_VC_POLICY);
      }
  
      @Override
      public void provideVmTags(long vmId, long volumeId, String tagValue) {
          VolumeVO volume = volumeDao.findById(volumeId);
          StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
          if (poolVO != null) {
              try {
                  SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
                  String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
                  SpApiResponse resp = StorPoolUtil.volumeUpdateVCTags(volName, conn, getVcPolicyTag(vmId));
                  if (resp.getError() != null) {
                      logger.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid()));
                  }
              } catch (Exception e) {
                  logger.warn(String.format("Could not update Virtual machine tags due to %s", e.getMessage()));
              }
          }
      }
  
      @Override
      public boolean isStorageSupportHA(StoragePoolType type) {
          return true;
      }
  
      @Override
      public void detachVolumeFromAllStorageNodes(Volume volume) {
          StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
          if (poolVO != null) {
              SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
              String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
              SpApiResponse resp = StorPoolUtil.detachAllForced(volName, false, conn);
              StorPoolUtil.spLog("The volume [%s] is detach from all clusters [%s]", volName, resp);
          }
      }
  
      @Override
      public boolean informStorageForDiskOfferingChange() {
          return true;
      }
  
      @Override
      public void updateStorageWithTheNewDiskOffering(Volume volume, DiskOffering newDiskOffering) {
          if (newDiskOffering == null) {
              return;
          }
  
          StoragePoolVO pool = primaryStoreDao.findById(volume.getPoolId());
          if (pool == null) {
              return;
          }
  
          String tier = getTierFromOfferingDetail(newDiskOffering.getId());
          String template = null;
          if (tier == null) {
              template = getTemplateFromOfferingDetail(newDiskOffering.getId());
          }
          if (tier == null && template == null) {
              return;
          }
          SpConnectionDesc conn = StorPoolUtil.getSpConnection(pool.getUuid(), pool.getId(), storagePoolDetailsDao, primaryStoreDao);
          StorPoolUtil.spLog("Updating volume [%s] with tier tag [%s] or template [%s] from Disk offering", volume.getId(), tier, template);
          String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
          Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, null, null, tier);
          StorPoolVolumeDef spVolume = new StorPoolVolumeDef(volumeName, null, tags, null, null, template, null, null, null);
          SpApiResponse response = StorPoolUtil.volumeUpdate(spVolume, conn);
          if (response.getError() != null) {
              StorPoolUtil.spLog("Could not update volume [%s] with tier tag [%s] or template [%s] from Disk offering due to [%s]", volume.getId(), tier, template, response.getError());
          }
      }
  
      private String getTemplateFromOfferingDetail(Long diskOfferingId) {
          String template = null;
          DiskOfferingDetailVO diskOfferingDetail = diskOfferingDetailsDao.findDetail(diskOfferingId, StorPoolUtil.SP_TEMPLATE);
          if (diskOfferingDetail == null ) {
              ServiceOfferingVO serviceOffering = serviceOfferingDao.findServiceOfferingByComputeOnlyDiskOffering(diskOfferingId, true);
              if (serviceOffering != null) {
                  ServiceOfferingDetailsVO serviceOfferingDetail = serviceOfferingDetailDao.findDetail(serviceOffering.getId(), StorPoolUtil.SP_TEMPLATE);
                  if (serviceOfferingDetail != null) {
                      template = serviceOfferingDetail.getValue();
                  }
              }
          } else {
              template = diskOfferingDetail.getValue();
          }
          return template;
      }
  
      private String getTierFromOfferingDetail(Long diskOfferingId) {
          String tier = null;
          DiskOfferingDetailVO diskOfferingDetail = diskOfferingDetailsDao.findDetail(diskOfferingId, StorPoolUtil.SP_TIER);
          if (diskOfferingDetail == null ) {
              return tier;
          } else {
              tier = diskOfferingDetail.getValue();
          }
          return tier;
      }
  }
  