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 java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import javax.inject.Inject;
  
  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.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.storage.command.CommandResult;
  import org.apache.cloudstack.storage.command.CreateObjectAnswer;
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  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.util.DateraObject;
  import org.apache.cloudstack.storage.datastore.util.DateraUtil;
  import org.apache.cloudstack.storage.to.SnapshotObjectTO;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.cloud.agent.api.Answer;
  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.DiskTO;
  import com.cloud.dc.ClusterDetailsDao;
  import com.cloud.dc.ClusterDetailsVO;
  import com.cloud.dc.ClusterVO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.hypervisor.Hypervisor;
  import com.cloud.storage.ResizeVolumePayload;
  import com.cloud.storage.Snapshot;
  import com.cloud.storage.SnapshotVO;
  import com.cloud.storage.Storage;
  import com.cloud.storage.Storage.StoragePoolType;
  import com.cloud.storage.StoragePool;
  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 com.cloud.storage.dao.SnapshotDetailsVO;
  import com.cloud.storage.dao.VMTemplatePoolDao;
  import com.cloud.storage.dao.VolumeDao;
  import com.cloud.storage.dao.VolumeDetailsDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.db.GlobalLock;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.google.common.base.Preconditions;
  import com.google.common.primitives.Ints;
  
  import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
  
  public class DateraPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
      protected Logger logger = LogManager.getLogger(getClass());
      private static final int s_lockTimeInSeconds = 300;
      private static final int s_lowestHypervisorSnapshotReserve = 10;
  
      @Inject
      private ClusterDao _clusterDao;
      @Inject
      private ClusterDetailsDao _clusterDetailsDao;
      @Inject
      private HostDao _hostDao;
      @Inject
      private SnapshotDao _snapshotDao;
      @Inject
      private SnapshotDetailsDao _snapshotDetailsDao;
      @Inject
      private PrimaryDataStoreDao _storagePoolDao;
      @Inject
      private StoragePoolDetailsDao _storagePoolDetailsDao;
      @Inject
      private VolumeDao _volumeDao;
      @Inject
      private VMTemplatePoolDao tmpltPoolDao;
      @Inject
      private PrimaryDataStoreDao storagePoolDao;
      @Inject
      private VolumeDetailsDao volumeDetailsDao;
      @Inject
      private SnapshotDetailsDao snapshotDetailsDao;
      @Inject
      private VolumeDataFactory volumeDataFactory;
  
      /**
       * Returns a map which lists the capabilities that this storage device can
       * offer. Currently supported STORAGE_SYSTEM_SNAPSHOT: Has the ability to create
       * native snapshots CAN_CREATE_VOLUME_FROM_SNAPSHOT: Can create new volumes from
       * native snapshots. CAN_CREATE_VOLUME_FROM_VOLUME: Device can clone volumes.
       * This is used for template caching.
       * @return a Map<String,String> which determines the capabilities of the driver
       */
      @Override
      public Map<String, String> getCapabilities() {
          Map<String, String> mapCapabilities = new HashMap<>();
  
          mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
          mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
          mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
          mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_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 ChapInfo getChapInfo(DataObject dataObject) {
          // We don't support auth yet
          return null;
      }
  
      /**
       * Fetches an App Instance from Datera, throws exception if it doesn't find it
       * @param conn            Datera Connection
       * @param appInstanceName Name of the Aplication Instance
       * @return application instance
       */
      public DateraObject.AppInstance getDateraAppInstance(DateraObject.DateraConnection conn, String appInstanceName) {
  
          DateraObject.AppInstance appInstance = null;
          try {
              appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
          } catch (DateraObject.DateraError dateraError) {
              logger.warn("Error getting appInstance " + appInstanceName, dateraError);
              throw new CloudRuntimeException(dateraError.getMessage());
          }
  
          if (appInstance == null) {
              throw new CloudRuntimeException("App instance not found " + appInstanceName);
          }
  
          return appInstance;
      }
  
      /**
       * Given a {@code dataObject} this function makes sure that the {@code host} has
       * access to it. All hosts which are in the same cluster are added to an
       * initiator group and that group is assigned to the appInstance. If an
       * initiator group does not exist, it is created. If the host does not have an
       * initiator registered on dataera, that is created and added to the initiator
       * group
       * @param dataObject The volume that needs to be accessed
       * @param host       The host which needs to access the volume
       * @param dataStore  Identifies which primary storage the volume resides in
       * @return True if access is granted. False otherwise
       */
      @Override
      public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
  
          logger.debug("grantAccess() called");
  
          Preconditions.checkArgument(dataObject != null, "'dataObject' should not be 'null'");
          Preconditions.checkArgument(host != null, "'host' should not be 'null'");
          Preconditions.checkArgument(dataStore != null, "'dataStore' should not be 'null'");
  
          long storagePoolId = dataStore.getId();
  
          DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
          String appInstanceName = getAppInstanceName(dataObject);
          DateraObject.AppInstance appInstance = getDateraAppInstance(conn, appInstanceName);
  
          Preconditions.checkArgument(appInstance != null);
  
          long clusterId = host.getClusterId();
  
          ClusterVO cluster = _clusterDao.findById(clusterId);
  
          GlobalLock lock = GlobalLock.getInternLock(cluster.getUuid());
  
          if (!lock.lock(s_lockTimeInSeconds)) {
              logger.debug("Couldn't lock the DB (in grantAccess) on the following string: " + cluster.getUuid());
          }
  
          try {
  
              DateraObject.InitiatorGroup initiatorGroup = null;
              String initiatorGroupKey = DateraUtil.getInitiatorGroupKey(storagePoolId);
  
              List<HostVO> hosts = _hostDao.findByClusterId(clusterId);
  
              if (!DateraUtil.hostsSupport_iScsi(hosts)) {
                  logger.debug("hostsSupport_iScsi() :Host does NOT support iscsci");
                  return false;
              }
  
              // We don't have the initiator group, create one
              String initiatorGroupName = DateraUtil.INITIATOR_GROUP_PREFIX + "-" + cluster.getUuid();
              logger.debug("Will use initiator group " + String.valueOf(initiatorGroupName));
  
              initiatorGroup = DateraUtil.getInitiatorGroup(conn, initiatorGroupName);
  
              if (initiatorGroup == null) {
                  logger.debug("create initiator group " + String.valueOf(initiatorGroupName));
                  initiatorGroup = DateraUtil.createInitiatorGroup(conn, initiatorGroupName);
                  // Save it to the DB
                  ClusterDetailsVO clusterDetail = new ClusterDetailsVO(clusterId, initiatorGroupKey, initiatorGroupName);
                  _clusterDetailsDao.persist(clusterDetail);
  
              } else {
                  initiatorGroup = DateraUtil.getInitiatorGroup(conn, initiatorGroupName);
              }
  
              Preconditions.checkNotNull(initiatorGroup, "initiatorGroup should not be Null");
  
              // We create an initiator for every host in this cluster and add it to the
              // initiator group
              addClusterHostsToInitiatorGroup(conn, clusterId, initiatorGroupName);
  
              // assign the initiatorgroup to appInstance
  
              if (!isInitiatorGroupAssignedToAppInstance(conn, initiatorGroup, appInstance)) {
                  DateraUtil.assignGroupToAppInstance(conn, initiatorGroupName, appInstanceName);
                  int retries = DateraUtil.DEFAULT_RETRIES;
                  while (!isInitiatorGroupAssignedToAppInstance(conn, initiatorGroup, appInstance) && retries > 0) {
                      Thread.sleep(DateraUtil.POLL_TIMEOUT_MS);
                      retries--;
                  }
  
                  Preconditions.checkArgument(isInitiatorGroupAssignedToAppInstance(conn, initiatorGroup, appInstance),
                          "Initgroup is not assigned to appinstance");
                  // FIXME: Sleep anyways
                  logger.debug("sleep " + String.valueOf(DateraUtil.POLL_TIMEOUT_MS) + " msec for ACL to be applied");
  
                  Thread.sleep(DateraUtil.POLL_TIMEOUT_MS); // ms
                  logger.debug(
                          "Initiator group " + String.valueOf(initiatorGroupName) + " is assigned to " + appInstanceName);
  
              }
  
              return true;
          } catch (DateraObject.DateraError | UnsupportedEncodingException | InterruptedException dateraError) {
              logger.warn(dateraError.getMessage(), dateraError);
              throw new CloudRuntimeException("Unable to grant access to volume " + dateraError.getMessage());
          } finally {
              lock.unlock();
              lock.releaseRef();
          }
      }
  
      private void addClusterHostsToInitiatorGroup(DateraObject.DateraConnection conn, long clusterId,
              String initiatorGroupName) throws DateraObject.DateraError, UnsupportedEncodingException {
  
          List<HostVO> clusterHosts = _hostDao.findByClusterId(clusterId);
          DateraObject.InitiatorGroup initiatorGroup = DateraUtil.getInitiatorGroup(conn, initiatorGroupName);
  
          for (HostVO host : clusterHosts) {
  
              // check if we have an initiator for the host
              String iqn = host.getStorageUrl();
  
              DateraObject.Initiator initiator = DateraUtil.getInitiator(conn, iqn);
              String initiatorName = "";
              // initiator can not be found, create it
              if (initiator == null) {
  
                  initiatorName = DateraUtil.INITIATOR_PREFIX + "-" + host.getUuid();
                  initiator = DateraUtil.createInitiator(conn, initiatorName, iqn);
                  logger.debug("Initiator " + initiatorName + " with " + iqn + "added ");
  
              }
              Preconditions.checkNotNull(initiator);
  
              if (!DateraUtil.isInitiatorPresentInGroup(initiator, initiatorGroup)) {
                  logger.debug("Add " + initiatorName + " to " + initiatorGroupName);
                  DateraUtil.addInitiatorToGroup(conn, initiator.getPath(), initiatorGroupName);
              }
          }
      }
  
      /**
       * Checks if an initiator group is assigned to an appInstance
       * @param conn           Datera connection
       * @param initiatorGroup Initiator group to check
       * @param appInstance    App Instance
       * @return True if initiator group is assigned to app instnace, false otherwise
       * @throws DateraObject.DateraError
       */
  
      private boolean isInitiatorGroupAssignedToAppInstance(DateraObject.DateraConnection conn,
              DateraObject.InitiatorGroup initiatorGroup, DateraObject.AppInstance appInstance)
              throws DateraObject.DateraError {
  
          Map<String, DateraObject.InitiatorGroup> assignedInitiatorGroups = DateraUtil
                  .getAppInstanceInitiatorGroups(conn, appInstance.getName());
  
          Preconditions.checkNotNull(assignedInitiatorGroups);
  
          for (DateraObject.InitiatorGroup ig : assignedInitiatorGroups.values()) {
              if (initiatorGroup.getName().equals(ig.getName())) {
                  return true;
              }
          }
  
          return false;
      }
  
      /**
       * Removes access of the initiator group to which {@code host} belongs from the
       * appInstance given by {@code dataObject}
       * @param dataObject Datera volume
       * @param host       the host which is currently having access to the volume
       * @param dataStore  The primary store to which volume belongs
       */
      @Override
      public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
          logger.debug("revokeAccess() called");
  
          Preconditions.checkArgument(dataObject != null, "'dataObject' should not be 'null'");
          Preconditions.checkArgument(host != null, "'host' should not be 'null'");
          Preconditions.checkArgument(dataStore != null, "'dataStore' should not be 'null'");
  
          String appInstanceName = getAppInstanceName(dataObject);
          long clusterId = host.getClusterId();
          long storagePoolId = dataStore.getId();
  
          ClusterVO cluster = _clusterDao.findById(clusterId);
  
          GlobalLock lock = GlobalLock.getInternLock(cluster.getUuid());
  
          if (!lock.lock(s_lockTimeInSeconds)) {
              logger.debug("Couldn't lock the DB (in revokeAccess) on the following string: " + cluster.getUuid());
          }
  
          try {
  
              String initiatorGroupName = DateraUtil.INITIATOR_GROUP_PREFIX + "-" + cluster.getUuid();
  
              DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
              DateraObject.AppInstance appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
              DateraObject.InitiatorGroup initiatorGroup = DateraUtil.getInitiatorGroup(conn, initiatorGroupName);
  
              if (initiatorGroup != null && appInstance != null) {
  
                  DateraUtil.removeGroupFromAppInstance(conn, initiatorGroupName, appInstanceName);
                  int retries = DateraUtil.DEFAULT_RETRIES;
                  while (isInitiatorGroupAssignedToAppInstance(conn, initiatorGroup, appInstance) && retries > 0) {
                      Thread.sleep(DateraUtil.POLL_TIMEOUT_MS);
                      retries--;
                  }
              }
  
          } catch (DateraObject.DateraError | UnsupportedEncodingException | InterruptedException dateraError) {
              String errMesg = String.format("Error revoking access for Volume : %s", dataObject);
              logger.warn(errMesg, dateraError);
              throw new CloudRuntimeException(errMesg);
          } finally {
              lock.unlock();
              lock.releaseRef();
          }
      }
  
      /**
       * Returns the size of template on this primary storage. If we already have a
       * template on this storage, we return 0
       * @param templateInfo Information about the template
       * @param storagePool  The pool where we want to store the template
       * @return Size in bytes
       */
      @Override
      public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) {
  
          List<VMTemplateStoragePoolVO> lstTemplatePoolRefs = tmpltPoolDao.listByPoolId(storagePool.getId());
  
          if (lstTemplatePoolRefs != null) {
              for (VMTemplateStoragePoolVO templatePoolRef : lstTemplatePoolRefs) {
                  if (templatePoolRef.getTemplateId() == templateInfo.getId()) {
                      // This indicates that we already have this template stored on this primary
                      // storage, so
                      // we do not require additional space.
                      return 0;
                  }
              }
          }
  
          // This indicates that we do not have a copy of this template on this primary
          // storage, so
          // we need to take it into consideration from a space standpoint (ex. when a new
          // VM is spun
          // up and wants to use this particular template for its root disk).
          return getDataObjectSizeIncludingHypervisorSnapshotReserve(templateInfo, storagePool);
      }
  
      /**
       * Returns Datera appInstanceName
       * @param dataObject volume or template
       * @return Derived Datera appInstanceName based on dataObject, Eg.
       *         CS-V-ROOT-123-6db58e3f-14c4-45ac-95e9-60e3a00ce7d0
       */
      private String getAppInstanceName(DataObject dataObject) {
  
          ArrayList<String> name = new ArrayList<>();
  
          name.add(DateraUtil.APPINSTANCE_PREFIX); // CS
  
          String dataObjectTypeString = dataObject.getType().name(); // TEMPLATE, VOLUME, SNAPSHOT
          String dataObjectTypeBrief;
          dataObjectTypeBrief = StringUtils.substring(dataObjectTypeString, 0, 1);
          name.add(dataObjectTypeBrief); // T, V
  
          switch (dataObject.getType()) {
          case TEMPLATE:
              TemplateInfo templateInfo = (TemplateInfo) dataObject;
  
              name.add(dataObject.getUuid()); // 6db58e3f-14c4-45ac-95e9-60e3a00ce7d0
  
              // For cached templates, we will also add the storage pool ID
              name.add(String.valueOf(dataObject.getDataStore().getId()));
              break;
  
          case VOLUME:
              VolumeInfo volumeInfo = (VolumeInfo) dataObject;
              String volumeName = volumeInfo.getName();
              name.add(String.valueOf(volumeName));
              name.add(dataObject.getUuid()); // 6db58e3f-14c4-45ac-95e9-60e3a00ce7d0
  
              VolumeVO volumeVo = _volumeDao.findById(dataObject.getId());
              logger.debug("volumeName : " + volumeName);
              break;
  
          case SNAPSHOT:
              name.add(dataObject.getUuid()); // 6db58e3f-14c4-45ac-95e9-60e3a00ce7d0
  
          }
  
          String appInstanceName = StringUtils.join("-", name.toArray());
          return StringUtils.substring(appInstanceName, 0, DateraUtil.APPINSTANCE_MAX_LENTH);
      }
  
      // Not being used right now as Datera doesn't support min IOPS
      private long getDefaultMinIops(long storagePoolId) {
          StoragePoolDetailVO storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId,
                  DateraUtil.CLUSTER_DEFAULT_MIN_IOPS);
  
          String clusterDefaultMinIops = storagePoolDetail.getValue();
  
          return Long.parseLong(clusterDefaultMinIops);
      }
  
      /**
       * If user doesn't specify the IOPS, use this IOPS
       * @param storagePoolId the primary storage
       * @return default max IOPS for this storage configured when the storage is
       *         added
       */
      private long getDefaultMaxIops(long storagePoolId) {
          StoragePoolDetailVO storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId,
                  DateraUtil.CLUSTER_DEFAULT_MAX_IOPS);
  
          String clusterDefaultMaxIops = storagePoolDetail.getValue();
  
          return Long.parseLong(clusterDefaultMaxIops);
      }
  
      /**
       * Return the default number of replicas to use (configured at storage addition
       * time)
       * @param storagePoolId the primary storage
       * @return the number of replicas to use
       */
      private int getNumReplicas(long storagePoolId) {
          StoragePoolDetailVO storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId,
                  DateraUtil.NUM_REPLICAS);
  
          String clusterDefaultReplicas = storagePoolDetail.getValue();
  
          return Integer.parseInt(clusterDefaultReplicas);
  
      }
  
      /**
       * Return the default volume placement to use (configured at storage addition
       * time)
       * @param storagePoolId the primary storage
       * @return volume placement string
       */
      private String getVolPlacement(long storagePoolId) {
          StoragePoolDetailVO storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId,
                  DateraUtil.VOL_PLACEMENT);
  
          String clusterDefaultVolPlacement = storagePoolDetail.getValue();
  
          return clusterDefaultVolPlacement;
  
      }
  
      /**
       * Return the default IP pool name to use (configured at storage addition time)
       * @param storagePoolId the primary storage
       * @return IP pool name
       */
      private String getIpPool(long storagePoolId) {
          String ipPool = DateraUtil.DEFAULT_IP_POOL;
          StoragePoolDetailVO storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId, DateraUtil.IP_POOL);
          if (storagePoolDetail != null) {
              ipPool = storagePoolDetail.getValue();
          }
          logger.debug("ipPool: " + ipPool);
          return ipPool;
  
      }
  
      @Override
      public long getUsedBytes(StoragePool storagePool) {
          return getUsedBytes(storagePool, Long.MIN_VALUE);
      }
  
      /**
       * Get the total space used by all the entities on the storage.
       * Total space = volume space + snapshot space + template space
       * @param storagePool      Primary storage
       * @param volumeIdToIgnore Ignore this volume (used when we delete a volume and
       *                         want to update the space)
       * @return size in bytes
       */
      private long getUsedBytes(StoragePool storagePool, long volumeIdToIgnore) {
          long usedSpaceBytes = 0;
  
          List<VolumeVO> lstVolumes = _volumeDao.findByPoolId(storagePool.getId(), null);
  
          if (lstVolumes != null) {
              for (VolumeVO volume : lstVolumes) {
                  if (volume.getId() == volumeIdToIgnore) {
                      continue;
                  }
  
                  VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volume.getId(), DateraUtil.VOLUME_SIZE);
  
                  if (volumeDetail != null && volumeDetail.getValue() != null) {
                      long volumeSizeGib = Long.parseLong(volumeDetail.getValue());
                      long volumeSizeBytes = DateraUtil.gibToBytes((int) (volumeSizeGib));
                      usedSpaceBytes += volumeSizeBytes;
                  } else {
                      DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePool.getId(),
                              _storagePoolDetailsDao);
                      try {
  
                          String appInstanceName = getAppInstanceName(volumeDataFactory.getVolume(volume.getId()));
                          DateraObject.AppInstance appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
                          if (appInstance != null) {
                              usedSpaceBytes += DateraUtil.gibToBytes(appInstance.getSize());
                          }
                      } catch (DateraObject.DateraError dateraError) {
                          String errMesg = String.format("Error getting used bytes for storage pool : %s", storagePool);
                          logger.warn(errMesg, dateraError);
                          throw new CloudRuntimeException(errMesg);
                      }
                  }
              }
          }
  
          List<SnapshotVO> lstSnapshots = _snapshotDao.listAll();
  
          if (lstSnapshots != null) {
              for (SnapshotVO snapshot : lstSnapshots) {
                  SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshot.getId(),
                          DateraUtil.STORAGE_POOL_ID);
  
                  // if this snapshot belongs to the storagePool that was passed in
                  if (snapshotDetails != null && snapshotDetails.getValue() != null
                          && Long.parseLong(snapshotDetails.getValue()) == storagePool.getId()) {
                      snapshotDetails = _snapshotDetailsDao.findDetail(snapshot.getId(), DateraUtil.VOLUME_SIZE);
  
                      if (snapshotDetails != null && snapshotDetails.getValue() != null) {
                          long snapshotSize = Long.parseLong(snapshotDetails.getValue());
  
                          usedSpaceBytes += snapshotSize;
                      }
                  }
              }
          }
  
          List<VMTemplateStoragePoolVO> lstTemplatePoolRefs = tmpltPoolDao.listByPoolId(storagePool.getId());
  
          if (lstTemplatePoolRefs != null) {
              for (VMTemplateStoragePoolVO templatePoolRef : lstTemplatePoolRefs) {
                  usedSpaceBytes += templatePoolRef.getTemplateSize();
              }
          }
          logger.debug("usedSpaceBytes: " + toHumanReadableSize(usedSpaceBytes));
  
          return usedSpaceBytes;
      }
  
      /**
       * Get total IOPS used by the storage array. Since Datera doesn't support min
       * IOPS, return zero for now
       * @param storagePool primary storage
       * @return total IOPS used
       */
      @Override
      public long getUsedIops(StoragePool storagePool) {
          long usedIops = 0;
          return usedIops;
      }
  
      /**
       * Rreturns the size of the volume including the hypervisor snapshot reserve
       * (HSR).
       * @param dataObject Volume or a Template
       * @param pool       primary storage where it resides
       * @return size in bytes
       */
  
      @Override
      public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) {
  
          long volumeSize = 0;
  
          switch (dataObject.getType()) {
          case VOLUME:
  
              VolumeInfo volume = (VolumeInfo) dataObject;
              volumeSize = volume.getSize();
              Integer hypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve();
  
              if (hypervisorSnapshotReserve != null) {
                  hypervisorSnapshotReserve = Math.max(hypervisorSnapshotReserve, s_lowestHypervisorSnapshotReserve);
                  volumeSize += volumeSize * (hypervisorSnapshotReserve / 100f);
              }
              logger.debug("Volume size: " + toHumanReadableSize(volumeSize));
              break;
  
          case TEMPLATE:
  
              TemplateInfo templateInfo = (TemplateInfo) dataObject;
              long templateSize = templateInfo.getSize() != null ? templateInfo.getSize() : 0;
  
              if (templateInfo.getHypervisorType() == Hypervisor.HypervisorType.KVM) {
                  volumeSize = templateSize;
              } else {
                  volumeSize = (long) (templateSize + templateSize * (s_lowestHypervisorSnapshotReserve / 100f));
              }
              logger.debug("Template volume size:" + toHumanReadableSize(volumeSize));
  
              break;
          }
          return volumeSize;
      }
  
      /**
       * Deletes a volume from Datera. If we are using native snapshots, we first
       * check if the volume is holding a native snapshot, if it does, then we don't
       * delete it from Datera but instead mark it so that when the snapshot is
       * deleted, we delete the volume
       *
       * @param volumeInfo    The volume which needs to be deleted
       * @param storagePoolId Primary storage where volume resides
       */
      private void deleteVolume(VolumeInfo volumeInfo, long storagePoolId) {
  
          DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
          Long volumeStoragePoolId = volumeInfo.getPoolId();
          long volumeId = volumeInfo.getId();
  
          if (volumeStoragePoolId == null) {
              return; // this volume was never assigned to a storage pool, so no SAN volume should
                      // exist for it
          }
  
          try {
  
              // If there are native snapshots on this appInstance, we want to keep it on
              // Datera
              // but remove it from cloudstack
              if (shouldDeleteVolume(volumeId, null)) {
                  DateraUtil.deleteAppInstance(conn, getAppInstanceName(volumeInfo));
              }
  
              volumeDetailsDao.removeDetails(volumeId);
  
              StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
  
              long usedBytes = getUsedBytes(storagePool, volumeId);
              storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
              storagePoolDao.update(storagePoolId, storagePool);
  
          } catch (UnsupportedEncodingException | DateraObject.DateraError e) {
              String errMesg = String.format("Error deleting app instance for Volume: %s", volumeInfo);
              logger.warn(errMesg, e);
              throw new CloudRuntimeException(errMesg);
          }
      }
  
      /**
       * given a {@code volumeInfo} and {@code storagePoolId}, creates an App instance
       * on Datera. Updates the usedBytes count in the DB for this storage pool. A
       * volume could be created in 3 ways
       *
       * 1) A fresh volume with no data: New volume created from Cloudstack
       *
       * 2) A volume created from a native snapshot. This is used when creating volume
       * from snapshot and native snapshots are supported
       *
       * 3) A volume created by cloning from another volume: This is used when
       * creating volume from template or volume from snapshot stored as another
       * volume when native snapshots are not supported by the hypervisor
       *
       *
       * @param volumeInfo    Info about the volume like size,QoS
       * @param storagePoolId The pool to create the vo
       * @return returns the IQN path which will be used by storage substem
       *
       */
  
      private String createVolume(VolumeInfo volumeInfo, long storagePoolId) {
          logger.debug("createVolume() called");
  
          Preconditions.checkArgument(volumeInfo != null, "volumeInfo cannot be null");
          Preconditions.checkArgument(storagePoolId > 0, "storagePoolId should be > 0");
  
          verifySufficientBytesForStoragePool(volumeInfo, storagePoolId);
  
          DateraObject.AppInstance appInstance;
  
          DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
          long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
          long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
          logger.debug("csTemplateId is " + String.valueOf(csTemplateId));
  
          try {
  
              if (csSnapshotId > 0) {
                  // creating volume from snapshot. The snapshot could either be a native snapshot
                  // or another volume.
                  logger.debug("Creating volume from snapshot ");
                  appInstance = createDateraClone(conn, csSnapshotId, volumeInfo, storagePoolId, DataObjectType.SNAPSHOT);
  
              } else if (csTemplateId > 0) {
  
                  // create volume from template. Invoked when creating new ROOT volume
                  logger.debug("Creating volume from template ");
  
                  appInstance = createDateraClone(conn, csTemplateId, volumeInfo, storagePoolId, DataObjectType.TEMPLATE);
                  String appInstanceName = appInstance.getName();
  
                  long volumeSize = getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo,
                          storagePoolDao.findById(storagePoolId));
  
                  // expand the template
                  if (volumeSize > DateraUtil.gibToBytes(appInstance.getSize())) {
  
                      // Expand the volume to include HSR depending on the volume's service offering
                      DateraUtil.updateAppInstanceSize(conn, appInstanceName, DateraUtil.bytesToGib(volumeSize));
  
                      // refresh appInstance
                      appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
  
                      Preconditions.checkNotNull(appInstance);
                      // update IOPS
                      if ((volumeInfo.getMaxIops() != null) && (volumeInfo.getMaxIops() != appInstance.getTotalIops())) {
                          int newIops = Ints.checkedCast(volumeInfo.getMaxIops());
                          DateraUtil.updateAppInstanceIops(conn, appInstanceName, newIops);
                      }
                      // refresh appInstance
                      appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
                  }
  
              } else {
                  // Just create a standard volume
                  logger.debug("Creating a standard volume ");
                  appInstance = createDateraVolume(conn, volumeInfo, storagePoolId);
              }
          } catch (UnsupportedEncodingException | DateraObject.DateraError e) {
              String errMesg = "Unable to create Volume Error: " + e.getMessage();
              logger.warn(errMesg);
              throw new CloudRuntimeException(errMesg, e);
          }
  
          if (appInstance == null) {
              String errMesg = "appInstance returned null";
              logger.warn(errMesg);
              throw new CloudRuntimeException(errMesg);
          }
  
          Preconditions.checkNotNull(appInstance);
          String iqn = appInstance.getIqn();
          String iqnPath = DateraUtil.generateIqnPath(iqn);
  
          VolumeVO volumeVo = _volumeDao.findById(volumeInfo.getId());
          logger.debug(String.format("volume: %s", volumeInfo));
  
          volumeVo.set_iScsiName(iqnPath);
          volumeVo.setFolder(appInstance.getName());
          volumeVo.setPoolType(Storage.StoragePoolType.IscsiLUN);
          volumeVo.setPoolId(storagePoolId);
  
          _volumeDao.update(volumeVo.getId(), volumeVo);
  
          updateVolumeDetails(volumeVo.getId(), appInstance.getSize());
  
          StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
  
          long capacityBytes = storagePool.getCapacityBytes();
          long usedBytes = getUsedBytes(storagePool);
  
          storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
  
          _storagePoolDao.update(storagePoolId, storagePool);
  
          return appInstance.getIqn();
      }
  
      /**
       * Helper function to create a Datera app instance. Throws an exception if
       * unsuccessful
       * @param conn          Datera connection
       * @param volumeInfo    Volume information
       * @param storagePoolId primary storage
       * @return The AppInstance which is created
       * @throws UnsupportedEncodingException
       * @throws                              DateraObject.DateraError
       */
      private DateraObject.AppInstance createDateraVolume(DateraObject.DateraConnection conn, VolumeInfo volumeInfo,
              long storagePoolId) throws UnsupportedEncodingException, DateraObject.DateraError {
  
          logger.debug("createDateraVolume() called");
          DateraObject.AppInstance appInstance = null;
          try {
  
              int minIops = Ints.checkedCast(
                      volumeInfo.getMinIops() != null ? volumeInfo.getMinIops() : getDefaultMinIops(storagePoolId));
  
              // int minIops = Ints.checkedCast(volumeInfo.getMinIops());
  
              int maxIops = Ints.checkedCast(
                      volumeInfo.getMaxIops() != null ? volumeInfo.getMaxIops() : getDefaultMaxIops(storagePoolId));
  
              // int maxIops = Ints.checkedCast(volumeInfo.getMaxIops());
  
              if (maxIops <= 0) { // We don't care about min iops for now
                  maxIops = Ints.checkedCast(getDefaultMaxIops(storagePoolId));
              }
  
              int replicas = getNumReplicas(storagePoolId);
              String volumePlacement = getVolPlacement(storagePoolId);
              String ipPool = getIpPool(storagePoolId);
  
              long volumeSizeBytes = getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo,
                      _storagePoolDao.findById(storagePoolId));
              int volumeSizeGib = DateraUtil.bytesToGib(volumeSizeBytes);
              if (volumePlacement == null) {
                  appInstance = DateraUtil.createAppInstance(conn, getAppInstanceName(volumeInfo), volumeSizeGib, maxIops,
                          replicas);
              } else {
                  appInstance = DateraUtil.createAppInstance(conn, getAppInstanceName(volumeInfo), volumeSizeGib, maxIops,
                          replicas, volumePlacement, ipPool);
              }
          } catch (Exception ex) {
              logger.debug("createDateraVolume() failed");
              logger.error(ex);
          }
          return appInstance;
      }
  
      /**
       * This function creates a new AppInstance on datera by cloning. We can clone
       * either from a volume snapshot (in case of native snapshots) or clone from
       * another app Instance in case of templates or snapshots as volumes
       *
       * @param conn          Datera Connection
       * @param dataObjectId  The ID of the clone, used to fetch details on how to
       *                      clone
       * @param volumeInfo    Information about the clone
       * @param storagePoolId Primary store to create the clone on
       * @param dataType      Type of the source (snapshot or template)
       * @return The cloned AppInstance
       */
      private DateraObject.AppInstance createDateraClone(DateraObject.DateraConnection conn, long dataObjectId,
              VolumeInfo volumeInfo, long storagePoolId, DataObjectType dataType)
              throws UnsupportedEncodingException, DateraObject.DateraError {
  
          logger.debug("createDateraClone() called");
  
          String clonedAppInstanceName = getAppInstanceName(volumeInfo);
          String baseAppInstanceName = null;
          DateraObject.AppInstance appInstance = null;
          String ipPool = getIpPool(storagePoolId);
  
          if (dataType == DataObjectType.SNAPSHOT) {
              SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(dataObjectId, DateraUtil.SNAPSHOT_ID);
  
              // Clone volume from a snapshot
              if (snapshotDetails != null && snapshotDetails.getValue() != null) {
                  logger.debug("Clone volume from a snapshot");
  
                  appInstance = DateraUtil.cloneAppInstanceFromSnapshot(conn, clonedAppInstanceName,
                          snapshotDetails.getValue(), ipPool);
  
                  if (volumeInfo.getMaxIops() != null) {
  
                      int totalIops = Math.min(DateraUtil.MAX_IOPS, Ints.checkedCast(volumeInfo.getMaxIops()));
                      DateraUtil.updateAppInstanceIops(conn, clonedAppInstanceName, totalIops);
                      appInstance = DateraUtil.getAppInstance(conn, clonedAppInstanceName);
                  }
  
                  if (appInstance == null) {
                      throw new CloudRuntimeException("Unable to create an app instance from snapshot "
                              + volumeInfo.getId() + " type " + dataType);
                  }
                  return appInstance;
  
              } else {
  
                  // Clone volume from an appInstance
                  logger.debug("Clone volume from an appInstance");
  
                  snapshotDetails = snapshotDetailsDao.findDetail(dataObjectId, DateraUtil.VOLUME_ID);
                  baseAppInstanceName = snapshotDetails.getValue();
  
              }
          } else if (dataType == DataObjectType.TEMPLATE) {
              logger.debug("Clone volume from a template");
  
              VMTemplateStoragePoolVO templatePoolRef = tmpltPoolDao.findByPoolTemplate(storagePoolId, dataObjectId, null);
  
              if (templatePoolRef != null) {
                  baseAppInstanceName = templatePoolRef.getLocalDownloadPath();
              }
          }
  
          if (baseAppInstanceName == null) {
              throw new CloudRuntimeException(
                      "Unable to find a base volume to clone " + volumeInfo.getUuid() + " type " + dataType);
          }
  
          // Clone the app Instance
          appInstance = DateraUtil.cloneAppInstanceFromVolume(conn, clonedAppInstanceName, baseAppInstanceName, ipPool);
  
          if (dataType == DataObjectType.TEMPLATE) {
              // Only update volume parameters if clone from cached template
              // Update maxIops
              if (volumeInfo.getMaxIops() != null) {
  
                  int totalIops = Math.min(DateraUtil.MAX_IOPS, Ints.checkedCast(volumeInfo.getMaxIops()));
  
                  DateraUtil.updateAppInstanceIops(conn, clonedAppInstanceName, totalIops);
                  appInstance = DateraUtil.getAppInstance(conn, clonedAppInstanceName);
              }
              // Update placementMode
              String newPlacementMode = getVolPlacement(storagePoolId);
              if (newPlacementMode != null) {
                  DateraUtil.updateAppInstancePlacement(conn, clonedAppInstanceName, newPlacementMode);
              }
              appInstance = DateraUtil.getAppInstance(conn, clonedAppInstanceName);
          }
          if (appInstance == null) {
              throw new CloudRuntimeException("Unable to create an app instance from snapshot or template "
                      + volumeInfo.getUuid() + " type " + dataType);
          }
          logger.debug("Datera - Cloned " + baseAppInstanceName + " to " + clonedAppInstanceName);
  
          return appInstance;
      }
  
      /**
       * This function gets invoked when you want to do operations on a snapshot. The
       * snapshot could be a native snapshot and you want to create a template out of
       * it. Since snapshots don't have an IQN, we create a temp volume for this
       * snapshot which will be used to carry out further operations. This function
       * also handles deletion of temp volumes. A flag in the snapshot details table
       * decides which action is performed.
       *
       * @param snapshotInfo  snapshot on Datera
       * @param storagePoolId primary store ID
       */
      private void createTempVolume(SnapshotInfo snapshotInfo, long storagePoolId) {
          logger.debug("createTempVolume() from snapshot called");
          String ipPool = getIpPool(storagePoolId);
          long csSnapshotId = snapshotInfo.getId();
  
          SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.SNAPSHOT_ID);
  
          if (snapshotDetails == null || snapshotDetails.getValue() == null) {
              throw new CloudRuntimeException("'createTempVolume(SnapshotInfo, long)' should not be invoked unless "
                      + DateraUtil.SNAPSHOT_ID + " exists.");
          }
  
          DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
          snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
  
          if (snapshotDetails != null && snapshotDetails.getValue() != null
                  && snapshotDetails.getValue().equalsIgnoreCase("create")) {
  
              snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.SNAPSHOT_ID);
              String snapshotName = snapshotDetails.getValue();
  
              String clonedAppInstanceName = getAppInstanceName(snapshotInfo);
              DateraObject.AppInstance clonedAppInstance;
  
              try {
                  clonedAppInstance = DateraUtil.cloneAppInstanceFromSnapshot(conn, clonedAppInstanceName, snapshotName,
                          ipPool);
                  DateraUtil.pollAppInstanceAvailable(conn, clonedAppInstanceName);
              } catch (DateraObject.DateraError | UnsupportedEncodingException e) {
                  String errMesg = "Unable to create temp volume " + csSnapshotId + "Error:" + e.getMessage();
                  logger.error(errMesg, e);
                  throw new CloudRuntimeException(errMesg, e);
              }
  
              if (clonedAppInstance == null) {
                  throw new CloudRuntimeException("Unable to clone volume for snapshot " + snapshotName);
              }
              logger.debug("Temp app_instance " + clonedAppInstanceName + " created");
              addTempVolumeToDb(csSnapshotId, clonedAppInstanceName);
              handleSnapshotDetails(csSnapshotId, DiskTO.IQN, DateraUtil.generateIqnPath(clonedAppInstance.getIqn()));
  
          } else if (snapshotDetails != null && snapshotDetails.getValue() != null
                  && snapshotDetails.getValue().equalsIgnoreCase("delete")) {
  
              snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.VOLUME_ID);
              try {
                  logger.debug("Deleting temp app_instance " + snapshotDetails.getValue());
                  DateraUtil.deleteAppInstance(conn, snapshotDetails.getValue());
              } catch (UnsupportedEncodingException | DateraObject.DateraError dateraError) {
                  String errMesg = "Error deleting temp volume " + dateraError.getMessage();
                  throw new CloudRuntimeException(errMesg, dateraError);
              }
  
              removeTempVolumeFromDb(csSnapshotId);
  
              snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DiskTO.IQN);
              snapshotDetailsDao.remove(snapshotDetails.getId());
          } else {
              throw new CloudRuntimeException("Invalid state in 'createTempVolume(SnapshotInfo, long)'");
          }
      }
  
      /**
       * This function gets invoked when we want to create a volume that caches the
       * template on the primary storage. This 'template volume' will then be cloned
       * to create new ROOT volumes.
       *
       * @param templateInfo  Information about the template like id, size
       * @param storagePoolId the primary store to create this volume on
       * @return IQN of the template volume
       */
      public String createTemplateVolume(TemplateInfo templateInfo, long storagePoolId) {
          logger.debug("createTemplateVolume() as cache template called");
  
          verifySufficientBytesForStoragePool(templateInfo, storagePoolId);
  
          DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
          String iqn = null;
          String appInstanceName = null;
          try {
  
              long templateSizeBytes = getDataObjectSizeIncludingHypervisorSnapshotReserve(templateInfo,
                      storagePoolDao.findById(storagePoolId));
  
              logger.debug("cached VM template sizeBytes: " + toHumanReadableSize(templateSizeBytes));
  
              int templateSizeGib = DateraUtil.bytesToGib(templateSizeBytes);
  
              int templateIops = DateraUtil.MAX_IOPS;
              int replicaCount = getNumReplicas(storagePoolId);
              appInstanceName = getAppInstanceName(templateInfo);
              String volumePlacement = getVolPlacement(storagePoolId);
              String ipPool = getIpPool(storagePoolId);
  
              logger.debug("cached VM template app_instance: " + appInstanceName + " ipPool: " + ipPool + " sizeGib: " + String.valueOf(templateSizeGib));
              DateraObject.AppInstance appInstance = DateraUtil.createAppInstance(conn, appInstanceName, templateSizeGib,
                      templateIops, replicaCount, volumePlacement, ipPool);
  
              if (appInstance == null) {
                  throw new CloudRuntimeException(String.format("Unable to create Template volume %s", templateInfo.getUuid()));
              }
  
              iqn = appInstance.getIqn();
  
              VMTemplateStoragePoolVO templatePoolRef = tmpltPoolDao.findByPoolTemplate(storagePoolId,
                      templateInfo.getId(), null);
  
              templatePoolRef.setInstallPath(DateraUtil.generateIqnPath(iqn));
              templatePoolRef.setLocalDownloadPath(appInstance.getName());
              templatePoolRef.setTemplateSize(DateraUtil.gibToBytes(appInstance.getSize()));
  
              tmpltPoolDao.update(templatePoolRef.getId(), templatePoolRef);
  
              StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
  
              long capacityBytes = storagePool.getCapacityBytes();
  
              long usedBytes = getUsedBytes(storagePool);
  
              storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
  
              storagePoolDao.update(storagePoolId, storagePool);
  
          } catch (UnsupportedEncodingException | DateraObject.DateraError dateraError) {
              if (DateraObject.DateraErrorTypes.ConflictError.equals(dateraError)) {
                  String errMesg = "template app Instance " + appInstanceName + " exists";
                  logger.debug(errMesg, dateraError);
              } else {
                  String errMesg = "Unable to create template app Instance " + dateraError.getMessage();
                  logger.error(errMesg, dateraError);
                  throw new CloudRuntimeException(errMesg, dateraError);
              }
          }
          return DateraUtil.generateIqnPath(iqn);
      }
  
      /**
       * Entry point into the create logic. The storage subsystem call this method to
       * create various data objects (volume/snapshot/template)
       *
       * @param dataStore
       * @param dataObject
       * @param callback
       */
      @Override
      public void createAsync(DataStore dataStore, DataObject dataObject,
              AsyncCompletionCallback<CreateCmdResult> callback) {
          String iqn = null;
          String errMsg = null;
  
          try {
              if (dataObject.getType() == DataObjectType.VOLUME) {
                  logger.debug("createAsync - creating volume");
                  iqn = createVolume((VolumeInfo) dataObject, dataStore.getId());
              } else if (dataObject.getType() == DataObjectType.SNAPSHOT) {
                  logger.debug("createAsync - creating snapshot");
                  createTempVolume((SnapshotInfo) dataObject, dataStore.getId());
              } else if (dataObject.getType() == DataObjectType.TEMPLATE) {
                  logger.debug("createAsync - creating template");
                  iqn = createTemplateVolume((TemplateInfo) dataObject, dataStore.getId());
              } else {
                  errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync";
                  logger.error(errMsg);
              }
          } catch (Exception ex) {
              errMsg = ex.getMessage();
  
              logger.error(errMsg);
  
              if (callback == null) {
                  throw ex;
              }
          }
  
          if (callback != null) {
  
              CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg));
  
              result.setResult(errMsg);
  
              callback.complete(result);
          }
      }
  
      /**
       * Helper function which updates volume size in the volume_details table
       * @param volumeId   Volume information
       * @param volumeSize Size in GB
       */
      private void updateVolumeDetails(long volumeId, long volumeSize) {
          VolumeDetailVO volumeDetailVo = volumeDetailsDao.findDetail(volumeId, DateraUtil.VOLUME_SIZE);
  
          if (volumeDetailVo == null || volumeDetailVo.getValue() == null) {
              volumeDetailVo = new VolumeDetailVO(volumeId, DateraUtil.VOLUME_SIZE, String.valueOf(volumeSize), false);
  
              volumeDetailsDao.persist(volumeDetailVo);
          }
      }
  
      /**
       * Entrypoint for delete operations.
       *
       * @param dataStore  Primary storage
       * @param dataObject object to delete
       * @param callback   used for async, complete the callback after the operation
       *                   is done.
       */
      @Override
      public void deleteAsync(DataStore dataStore, DataObject dataObject,
              AsyncCompletionCallback<CommandResult> callback) {
          String errMsg = null;
  
          try {
              if (dataObject.getType() == DataObjectType.VOLUME) {
                  logger.debug("deleteAsync - deleting volume");
                  deleteVolume((VolumeInfo) dataObject, dataStore.getId());
              } else if (dataObject.getType() == DataObjectType.SNAPSHOT) {
                  logger.debug("deleteAsync - deleting snapshot");
                  deleteSnapshot((SnapshotInfo) dataObject, dataStore.getId());
              } else if (dataObject.getType() == DataObjectType.TEMPLATE) {
                  logger.debug("deleteAsync - deleting template");
                  deleteTemplate((TemplateInfo) dataObject, dataStore.getId());
              } else {
                  errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
              }
          } catch (Exception ex) {
              errMsg = ex.getMessage();
  
              logger.error(errMsg);
          }
  
          CommandResult result = new CommandResult();
  
          result.setResult(errMsg);
  
          callback.complete(result);
  
      }
  
      @Override
      public void copyAsync(DataObject srcData, DataObject destData,
              AsyncCompletionCallback<CopyCommandResult> callback) {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public void copyAsync(DataObject srcData, DataObject destData,
              Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public boolean canCopy(DataObject srcData, DataObject destData) {
          return false;
      }
  
      /**
       * Entry point for taking a snapshot. A native snpashot is taken if the
       * hypervisor supports it, otherwise a volume is created and the data is copied
       * via the hypervisor and Cloudstack will treat this volume as a snapshot.
       *
       * @param snapshotInfo Snapshot information
       * @param callback     Async context
       */
      @Override
      public void takeSnapshot(SnapshotInfo snapshotInfo, AsyncCompletionCallback<CreateCmdResult> callback) {
          logger.debug("takeSnapshot() called");
  
          CreateCmdResult result;
  
          try {
  
              VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
              VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
  
              long storagePoolId = volumeVO.getPoolId();
  
              DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
              String baseAppInstanceName = getAppInstanceName(volumeInfo);
  
              DateraObject.AppInstance baseAppInstance = DateraUtil.getAppInstance(conn, baseAppInstanceName);
  
              Preconditions.checkNotNull(baseAppInstance);
  
              SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO) snapshotInfo.getTO();
  
              if (shouldTakeSnapshot(snapshotInfo.getId())) {
  
                  DateraObject.VolumeSnapshot volumeSnapshot = DateraUtil.takeVolumeSnapshot(conn, baseAppInstanceName);
                  if (volumeSnapshot == null) {
                      logger.error(String.format("Unable to take native snapshot appInstance name: %s volume: %s", baseAppInstanceName, volumeInfo));
                      throw new CloudRuntimeException("Unable to take native snapshot for volume " + volumeInfo.getUuid());
                  }
  
                  String snapshotName = baseAppInstanceName + ":" + volumeSnapshot.getTimestamp();
                  updateSnapshotDetails(snapshotInfo.getId(), baseAppInstanceName, snapshotName, storagePoolId,
                          baseAppInstance.getSize());
  
                  snapshotObjectTo.setPath("DateraSnapshotId=" + snapshotName);
                  logger.info(" snapshot taken: " + snapshotName);
  
              } else {
  
                  StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
  
                  long capacityBytes = storagePool.getCapacityBytes();
                  long usedBytes = getUsedBytes(storagePool);
                  int volumeSizeGib = baseAppInstance.getSize();
                  long volumeSizeBytes = DateraUtil.gibToBytes(volumeSizeGib);
                  String volumePlacement = getVolPlacement(storagePoolId);
                  String ipPool = getIpPool(storagePoolId);
  
                  usedBytes += volumeSizeBytes;
  
                  if (usedBytes > capacityBytes) {
                      throw new CloudRuntimeException(
                              "Insufficient amount of space remains in this primary storage to create a snapshot volume");
                  }
  
                  String appInstanceName = getAppInstanceName(snapshotInfo);
                  DateraObject.AppInstance snapshotAppInstance = DateraUtil.createAppInstance(conn, appInstanceName,
                          volumeSizeGib, DateraUtil.MAX_IOPS, getNumReplicas(storagePoolId), volumePlacement, ipPool);
  
                  snapshotObjectTo.setPath(snapshotAppInstance.getName());
                  String iqnPath = DateraUtil.generateIqnPath(snapshotAppInstance.getIqn());
                  updateSnapshotDetails(snapshotInfo.getId(), snapshotAppInstance.getName(), storagePoolId,
                          snapshotAppInstance.getSize(), iqnPath);
  
                  snapshotObjectTo.setPath("DateraVolumeId=" + snapshotAppInstance.getName());
  
                  storagePool.setUsedBytes(usedBytes);
                  // update size in storage pool
                  _storagePoolDao.update(storagePoolId, storagePool);
              }
  
              CreateObjectAnswer createObjectAnswer = new CreateObjectAnswer(snapshotObjectTo);
  
              result = new CreateCmdResult(null, createObjectAnswer);
  
              result.setResult(null);
          } catch (Exception ex) {
              logger.debug(String.format("Failed to take CloudStack snapshot: %s", snapshotInfo), ex);
  
              result = new CreateCmdResult(null, new CreateObjectAnswer(ex.toString()));
  
              result.setResult(ex.toString());
          }
  
          callback.complete(result);
      }
  
      /**
       * If a native snapshot is used, this function updates the snapshot_detauls
       * table with the correct attributes
       *
       * @param csSnapshotId  Cloudstack snapshot ID
       * @param volumeId      Base volume ID
       * @param newSnapshotId SnapshotID on Datera (appInstanceName:Timestamp)
       * @param storagePoolId Primary storage
       * @param newVolumeSize VolumeSize in GB
       */
      private void updateSnapshotDetails(long csSnapshotId, String volumeId, String newSnapshotId, long storagePoolId,
              long newVolumeSize) {
          SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.VOLUME_ID,
                  String.valueOf(volumeId), false);
  
          snapshotDetailsDao.persist(snapshotDetail);
  
          snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.SNAPSHOT_ID, String.valueOf(newSnapshotId),
                  false);
  
          snapshotDetailsDao.persist(snapshotDetail);
  
          snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.STORAGE_POOL_ID, String.valueOf(storagePoolId),
                  false);
  
          snapshotDetailsDao.persist(snapshotDetail);
  
          snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.VOLUME_SIZE, String.valueOf(newVolumeSize),
                  false);
  
          snapshotDetailsDao.persist(snapshotDetail);
      }
  
      /**
       * If a snapshot is represented as a volume, this function updates the
       * snapshot_details table with the right attributes so that Cloudstack knows
       * that this snapshot is a volume on the backend
       *
       * @param csSnapshotId            Snapshot ID on Cloudstack
       * @param snapshotAppInstanceName snapshot name on Datera
       *                                <appInstanceName>:<Timestamp>
       * @param storagePoolId           primary storage
       * @param snapshotSizeGb          snapshotSize
       * @param snapshotIqn             IQN of snapshot
       */
      private void updateSnapshotDetails(long csSnapshotId, String snapshotAppInstanceName, long storagePoolId,
              long snapshotSizeGb, String snapshotIqn) {
          SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.VOLUME_ID,
                  String.valueOf(snapshotAppInstanceName), false);
  
          _snapshotDetailsDao.persist(snapshotDetail);
  
          snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.STORAGE_POOL_ID, String.valueOf(storagePoolId),
                  false);
  
          _snapshotDetailsDao.persist(snapshotDetail);
  
          snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DateraUtil.VOLUME_SIZE, String.valueOf(snapshotSizeGb),
                  false);
  
          _snapshotDetailsDao.persist(snapshotDetail);
  
          snapshotDetail = new SnapshotDetailsVO(csSnapshotId, DiskTO.IQN, snapshotIqn, false);
  
          _snapshotDetailsDao.persist(snapshotDetail);
      }
  
      /**
       * Deletes snapshot on Datera
       * @param snapshotInfo  snapshot information
       * @param storagePoolId primary storage
       * @throws UnsupportedEncodingException
       * @throws                              DateraObject.DateraError
       */
      private void deleteSnapshot(SnapshotInfo snapshotInfo, long storagePoolId)
              throws UnsupportedEncodingException, DateraObject.DateraError {
  
          long csSnapshotId = snapshotInfo.getId();
  
          try {
              DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
              SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.SNAPSHOT_ID);
  
              if (snapshotDetails != null && snapshotDetails.getValue() != null) {
                  // Native snapshot being used, delete that
  
                  String snapshotName = snapshotDetails.getValue();
  
                  DateraUtil.deleteVolumeSnapshot(conn, snapshotName);
  
                  // check if the underlying volume needs to be deleted
                  SnapshotVO snapshot = _snapshotDao.findById(csSnapshotId);
                  VolumeVO volume = _volumeDao.findById(snapshot.getVolumeId());
  
                  if (volume == null) {
  
                      // deleted from Cloudstack. Check if other snapshots are using this volume
                      volume = _volumeDao.findByIdIncludingRemoved(snapshot.getVolumeId());
  
                      if (shouldDeleteVolume(snapshot.getVolumeId(), snapshot.getId())) {
                          DateraUtil.deleteAppInstance(conn, volume.getFolder());
                      }
                  }
              } else {
  
                  // An App Instance is being used to support the CloudStack volume snapshot.
  
                  snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.VOLUME_ID);
                  String appInstanceName = snapshotDetails.getValue();
  
                  DateraUtil.deleteAppInstance(conn, appInstanceName);
              }
  
              snapshotDetailsDao.removeDetails(csSnapshotId);
  
              StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
  
              // getUsedBytes(StoragePool) will not include the snapshot to delete because it
              // has already been deleted by this point
              long usedBytes = getUsedBytes(storagePool);
  
              storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
  
              storagePoolDao.update(storagePoolId, storagePool);
          } catch (Exception ex) {
              logger.debug(String.format("Error in 'deleteSnapshot(SnapshotInfo, long)'. CloudStack snapshot: %s", snapshotInfo),
                      ex);
              throw ex;
          }
      }
  
      /**
       * Deletes a template from Datera
       * @param templateInfo  Information about Template
       * @param storagePoolId Primary storage
       * @throws UnsupportedEncodingException
       * @throws                              DateraObject.DateraError
       */
      private void deleteTemplate(TemplateInfo templateInfo, long storagePoolId)
              throws UnsupportedEncodingException, DateraObject.DateraError {
          try {
              DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
              String appInstanceName = getAppInstanceName(templateInfo);
  
              DateraUtil.deleteAppInstance(conn, appInstanceName);
  
              VMTemplateStoragePoolVO templatePoolRef = tmpltPoolDao.findByPoolTemplate(storagePoolId,
                      templateInfo.getId(), null);
  
              tmpltPoolDao.remove(templatePoolRef.getId());
  
              StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
  
              // getUsedBytes(StoragePool) will not include the template to delete because the
              // "template_spool_ref" table has already been updated by this point
              long usedBytes = getUsedBytes(storagePool);
  
              storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
  
              storagePoolDao.update(storagePoolId, storagePool);
          } catch (Exception ex) {
              logger.debug(String.format("Failed to delete template volume. CloudStack template: %s", templateInfo), ex);
  
              throw ex;
          }
      }
  
      /**
       * Revert snapshot for a volume
       *
       * @param snapshotInfo           Information about volume snapshot
       * @param snapshotOnPrimaryStore Not used
       * @throws CloudRuntimeException
       */
      @Override
      public void revertSnapshot(SnapshotInfo snapshotInfo, SnapshotInfo snapshotOnPrimaryStore,
              AsyncCompletionCallback<CommandResult> callback) {
  
          VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
          VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
  
          long storagePoolId = volumeVO.getPoolId();
          long csSnapshotId = snapshotInfo.getId();
          logger.info(String.format("Datera - restoreVolumeSnapshot from snapshot %s to volume %s", snapshotInfo, volumeVO));
  
          DateraObject.AppInstance appInstance;
  
          try {
  
              if (volumeVO == null || volumeVO.getRemoved() != null) {
                  String errMsg = "The volume that the snapshot belongs to no longer exists.";
  
                  CommandResult commandResult = new CommandResult();
  
                  commandResult.setResult(errMsg);
  
                  callback.complete(commandResult);
  
                  return;
              }
  
              DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
              SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.SNAPSHOT_ID);
  
              if (snapshotDetails != null && snapshotDetails.getValue() != null) {
                  // Native snapshot being used, restore snapshot from Datera AppInstance
  
                  String snapshotName = snapshotDetails.getValue();
  
                  logger.info("Datera - restoreVolumeSnapshot: " + snapshotName);
  
                  appInstance = DateraUtil.restoreVolumeSnapshot(conn, snapshotName);
  
                  Preconditions.checkNotNull(appInstance);
  
                  updateVolumeDetails(volumeInfo.getId(), appInstance.getSize());
              }
  
              CommandResult commandResult = new CommandResult();
  
              callback.complete(commandResult);
  
          } catch (Exception ex) {
              logger.debug(String.format("Error in 'revertSnapshot()'. CloudStack snapshot: %s", snapshotInfo), ex);
              throw new CloudRuntimeException(ex.getMessage());
          }
  
      }
  
      /**
       * Resizes a volume on Datera, shrinking is not allowed. Resize also takes into
       * account the HSR
       * @param dataObject volume to resize
       * @param callback   async context
       */
      @Override
      public void resize(DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
          String iqn = null;
          String errMsg = null;
  
          if (dataObject.getType() == DataObjectType.VOLUME) {
              VolumeInfo volumeInfo = (VolumeInfo) dataObject;
              String iqnPath = volumeInfo.get_iScsiName();
              iqn = DateraUtil.extractIqn(iqnPath);
  
              long storagePoolId = volumeInfo.getPoolId();
              ResizeVolumePayload payload = (ResizeVolumePayload) volumeInfo.getpayload();
              String appInstanceName = getAppInstanceName(volumeInfo);
              long newSizeBytes = payload.newSize;
  
              Integer hsr = volumeInfo.getHypervisorSnapshotReserve();
  
              if (payload.newSize != null || payload.newHypervisorSnapshotReserve != null) {
                  if (payload.newHypervisorSnapshotReserve != null) {
                      if (hsr != null) {
                          if (payload.newHypervisorSnapshotReserve > hsr) {
                              hsr = payload.newHypervisorSnapshotReserve;
                          }
                      } else {
                          hsr = payload.newHypervisorSnapshotReserve;
                      }
                  }
  
                  newSizeBytes = getVolumeSizeIncludingHypervisorSnapshotReserve(payload.newSize, hsr);
              }
  
              int newSize = DateraUtil.bytesToGib(newSizeBytes);
  
              DateraObject.DateraConnection conn = DateraUtil.getDateraConnection(storagePoolId, _storagePoolDetailsDao);
  
              try {
  
                  DateraObject.AppInstance appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
  
                  Preconditions.checkNotNull(appInstance);
  
                  if (appInstance.getSize() < newSize) {
                      DateraUtil.updateAppInstanceSize(conn, appInstanceName, Ints.checkedCast(newSize));
                  }
  
                  if (payload.newMaxIops != null && appInstance.getTotalIops() != payload.newMaxIops) {
                      DateraUtil.updateAppInstanceIops(conn, appInstanceName, Ints.checkedCast(payload.newMaxIops));
                  }
  
                  appInstance = DateraUtil.getAppInstance(conn, appInstanceName);
  
                  Preconditions.checkNotNull(appInstance);
  
                  VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
  
                  volume.setMinIops(payload.newMinIops);
                  volume.setMaxIops(payload.newMaxIops);
  
                  _volumeDao.update(volume.getId(), volume);
  
                  updateVolumeDetails(volume.getId(), appInstance.getSize());
  
                  Preconditions.checkNotNull(appInstance);
  
              } catch (DateraObject.DateraError | UnsupportedEncodingException dateraError) {
                  dateraError.printStackTrace();
              }
  
          } else {
              errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to resize";
          }
  
          CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg));
  
          result.setResult(errMsg);
  
          callback.complete(result);
      }
  
      /**
       * Adding temp volume to the snapshot_details table. This is used if we are
       * using a native snapshot and we want to create a template out of the snapshot
       *
       * @param csSnapshotId   Source snasphot
       * @param tempVolumeName temp volume app instance on Datera
       */
      private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
          SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.VOLUME_ID);
  
          if (snapshotDetails == null || snapshotDetails.getValue() == null) {
              throw new CloudRuntimeException(
                      "'addTempVolumeId' should not be invoked unless " + DateraUtil.VOLUME_ID + " exists.");
          }
  
          String originalVolumeId = snapshotDetails.getValue();
  
          handleSnapshotDetails(csSnapshotId, DateraUtil.TEMP_VOLUME_ID, originalVolumeId);
          handleSnapshotDetails(csSnapshotId, DateraUtil.VOLUME_ID, tempVolumeName);
      }
  
      private void removeTempVolumeFromDb(long csSnapshotId) {
          SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DateraUtil.TEMP_VOLUME_ID);
  
          if (snapshotDetails == null || snapshotDetails.getValue() == null) {
              throw new CloudRuntimeException(
                      "'removeTempVolumeId' should not be invoked unless " + DateraUtil.TEMP_VOLUME_ID + " exists.");
          }
  
          String originalVolumeId = snapshotDetails.getValue();
  
          handleSnapshotDetails(csSnapshotId, DateraUtil.VOLUME_ID, originalVolumeId);
  
          snapshotDetailsDao.remove(snapshotDetails.getId());
      }
  
      /**
       * Helper function to update snapshot_details table
       *
       * @param csSnapshotId Snapshot
       * @param name         attribute name
       * @param value        attribute value
       */
      private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
          snapshotDetailsDao.removeDetail(csSnapshotId, name);
          SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
          snapshotDetailsDao.persist(snapshotDetails);
      }
  
      private void verifySufficientBytesForStoragePool(DataObject dataObject, long storagePoolId) {
          StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
  
          long requestedBytes = getDataObjectSizeIncludingHypervisorSnapshotReserve(dataObject, storagePool);
  
          verifySufficientBytesForStoragePool(requestedBytes, storagePoolId);
      }
  
      private void verifySufficientBytesForStoragePool(long requestedBytes, long storagePoolId) {
          StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
  
          long capacityBytes = storagePool.getCapacityBytes();
          long usedBytes = getUsedBytes(storagePool);
  
          usedBytes += requestedBytes;
  
          if (usedBytes > capacityBytes) {
              throw new CloudRuntimeException("Insufficient amount of space remains in this primary storage");
          }
      }
  
      /**
       * Returns true if we can take a native snapshot else returns false. Set in
       * StorageSystemSnapshotStrategy
       * @param snapshotId Snapshot
       * @return true if native snapshot, false otherwise
       */
      private boolean shouldTakeSnapshot(long snapshotId) {
          SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshotId, "takeSnapshot");
  
          if (snapshotDetails != null && snapshotDetails.getValue() != null) {
              return new Boolean(snapshotDetails.getValue());
          }
  
          return false;
      }
  
      private long getCsIdForCloning(long volumeId, String cloneOf) {
          VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, cloneOf);
  
          if (volumeDetail != null && volumeDetail.getValue() != null) {
              return new Long(volumeDetail.getValue());
          }
  
          return Long.MIN_VALUE;
      }
  
      /**
       * Checks if the volume can be safely deleted. ie it has no native snapshots
       * @param csVolumeId         Volume
       * @param snapshotToIgnoreId Used to check if this is the only snapshot on the
       *                           volume
       * @return true if we can delete, false otherwise
       */
      private boolean shouldDeleteVolume(Long csVolumeId, Long snapshotToIgnoreId) {
  
          List<SnapshotVO> lstSnapshots = getNonDestroyedSnapshots(csVolumeId);
  
          for (SnapshotVO snapshot : lstSnapshots) {
              if (snapshotToIgnoreId != null && snapshot.getId() == snapshotToIgnoreId) {
                  continue;
              }
              SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshot.getId(), DateraUtil.SNAPSHOT_ID);
  
              if (snapshotDetails != null && snapshotDetails.getValue() != null) {
                  return false;
              }
          }
  
          return true;
      }
  
      private List<SnapshotVO> getNonDestroyedSnapshots(long csVolumeId) {
          List<SnapshotVO> lstSnapshots = _snapshotDao.listByVolumeId(csVolumeId);
  
          if (lstSnapshots == null) {
              lstSnapshots = new ArrayList<>();
          }
  
          List<SnapshotVO> lstSnapshots2 = new ArrayList<>();
  
          for (SnapshotVO snapshot : lstSnapshots) {
              if (!Snapshot.State.Destroyed.equals(snapshot.getState())) {
                  lstSnapshots2.add(snapshot);
              }
          }
  
          return lstSnapshots2;
      }
  
      private long getVolumeSizeIncludingHypervisorSnapshotReserve(long volumeSize, Integer hypervisorSnapshotReserve) {
          if (hypervisorSnapshotReserve != null) {
              hypervisorSnapshotReserve = Math.max(hypervisorSnapshotReserve, s_lowestHypervisorSnapshotReserve);
  
              volumeSize += volumeSize * (hypervisorSnapshotReserve / 100f);
          }
  
          return volumeSize;
      }
  
      @Override
      public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo,
              QualityOfServiceState qualityOfServiceState) {
      }
  
      @Override
      public boolean canProvideStorageStats() {
          return false;
      }
  
      @Override
      public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
          return null;
      }
  
      @Override
      public boolean canProvideVolumeStats() {
          return false;
      }
  
      @Override
      public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
          return null;
      }
  
      @Override
      public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
          return true;
      }
  
      @Override
      public boolean isVmInfoNeeded() {
          return false;
      }
  
      @Override
      public void provideVmInfo(long vmId, long volumeId) {
      }
  
      @Override
      public boolean isVmTagsNeeded(String tagKey) {
          return false;
      }
  
      @Override
      public void provideVmTags(long vmId, long volumeId, String tagValue) {
      }
  
      @Override
      public boolean isStorageSupportHA(StoragePoolType type) {
          return false;
      }
  
      @Override
      public void detachVolumeFromAllStorageNodes(Volume volume) {
      }
  
      @Override
      public boolean volumesRequireGrantAccessWhenUsed() {
          return true;
      }
  }
  