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.util.HashMap;
  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.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.StoragePoolVO;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil.FileSystem;
  import org.apache.cloudstack.storage.to.SnapshotObjectTO;
  import org.apache.cloudstack.storage.volume.VolumeObject;
  
  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.host.Host;
  import com.cloud.storage.DiskOfferingVO;
  import com.cloud.storage.ResizeVolumePayload;
  import com.cloud.storage.Storage.StoragePoolType;
  import com.cloud.storage.StorageManager;
  import com.cloud.storage.StoragePool;
  import com.cloud.storage.VolumeDetailVO;
  import com.cloud.storage.VolumeVO;
  import com.cloud.storage.dao.DiskOfferingDao;
  import com.cloud.storage.dao.VolumeDao;
  import com.cloud.storage.dao.VolumeDetailsDao;
  import com.cloud.user.AccountManager;
  import com.cloud.utils.Pair;
  import com.cloud.utils.exception.CloudRuntimeException;
  
  /**
   * The implementation class for <code>ElastistorPrimaryDataStoreDriver</code>.
   * This directs the public interface methods to use CloudByte's Elastistor based
   * volumes.
   */
  public class ElastistorPrimaryDataStoreDriver extends CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
  
  
      @Inject
      AccountManager _accountMgr;
      @Inject
      DiskOfferingDao _diskOfferingDao;
      @Inject
      private VolumeDao _volumeDao;
      @Inject
      private PrimaryDataStoreDao _storagePoolDao;
      @Inject
      StorageManager storageMgr;
      @Inject
      VolumeDetailsDao _volumeDetailsDao;
  
      @Override
      public DataTO getTO(DataObject data) {
          return null;
      }
  
      @Override
      public DataStoreTO getStoreTO(DataStore store) {
          return null;
      }
  
      @Override
      public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
  
          String iqn = null;
          String errMsg = null;
  
          CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg));
  
          if (dataObject.getType() == DataObjectType.VOLUME) {
  
              VolumeInfo volumeInfo = (VolumeInfo) dataObject;
  
              long storagePoolId = dataStore.getId();
              String volumeName = volumeInfo.getName();
              Long Iops = volumeInfo.getMaxIops();
              // quota size of the cloudbyte volume will be increased with the given HypervisorSnapshotReserve
              Long quotaSize = getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo, _storagePoolDao.findById(storagePoolId));
  
              StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
              VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
  
              // if the primary storage is not managed (thick provisioned)
              // then no need to create volume at elastistor,
              // calling super(default) that creates a vdi(disk) only.
              if (!(storagePool.isManaged())) {
                  super.createAsync(dataStore, dataObject, callback);
  
                  // update the volume property
                  volume.setPoolType(storagePool.getPoolType());
                  _volumeDao.update(volume.getId(), volume);
  
                  return;
              }
  
              DiskOfferingVO diskOffering = _diskOfferingDao.findById(volumeInfo.getDiskOfferingId());
  
              long capacityIops = storagePool.getCapacityIops();
              capacityIops = capacityIops - Iops;
  
              if (capacityIops < 0) {
                  throw new CloudRuntimeException(String.format("IOPS not available. [pool:%s] [availiops:%d] [requirediops:%d]", storagePool, capacityIops, Iops));
              }
  
              String protocoltype = null;
              StoragePoolVO dataStoreVO = _storagePoolDao.findById(storagePoolId);
              String desc = diskOffering.getDisplayText();
  
              if (desc.toLowerCase().contains("iscsi")) {
                  protocoltype = "iscsi";
              } else if (dataStoreVO.getPoolType().equals(StoragePoolType.NetworkFilesystem) || dataStoreVO.getPoolType().equals(StoragePoolType.Filesystem)) {
                  protocoltype = "nfs";
              } else {
                  protocoltype = "iscsi";
              }
  
              FileSystem esvolume = null;
              try {
                  esvolume = ElastistorUtil.createElastistorVolume(volumeName, dataStoreVO.getUuid(), quotaSize, Iops, protocoltype, volumeName);
              } catch (Throwable e) {
                  logger.error(e.toString(), e);
                  result.setResult(e.toString());
                  callback.complete(result);
                  throw new CloudRuntimeException(e.getMessage());
              }
  
              if (esvolume.getNfsenabled().equalsIgnoreCase("true")) {
                  volume.set_iScsiName(esvolume.getPath());
                  volume.setPoolType(StoragePoolType.NetworkFilesystem);
              } else {
                  iqn = esvolume.getIqn();
                  String modifiediqn = "/" + iqn + "/0";
                  volume.set_iScsiName(modifiediqn);
                  volume.setPoolType(StoragePoolType.IscsiLUN);
              }
  
              volume.setFolder(String.valueOf(esvolume.getUuid()));
              volume.setPoolId(storagePoolId);
              volume.setUuid(esvolume.getUuid());
              volume.setPath(null);
  
              _volumeDao.update(volume.getId(), volume);
  
              // create new volume details for the volume
              //updateVolumeDetails(volume, esvolume);
  
              long capacityBytes = storagePool.getCapacityBytes();
              long usedBytes = storagePool.getUsedBytes();
  
              Long inbytes = volume.getSize();
  
              usedBytes += inbytes;
  
              storagePool.setCapacityIops(capacityIops);
              storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
  
              _storagePoolDao.update(storagePoolId, storagePool);
              logger.info("Elastistor volume creation complete.");
          } else {
              errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync";
              logger.error(errMsg);
          }
  
          result.setResult(errMsg);
  
          callback.complete(result);
      }
  
      @Override
      public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback) {
  
          String errMsg = null;
  
          StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
  
          // if the primary storage is not managed(thick provisioned) then no need
          // to delete volume at elastistor, just
          // call the super(default) to delete a vdi(disk) only.
  
          if (!(storagePool.isManaged())) {
              super.deleteAsync(dataStore, dataObject, callback);
              return;
          }
  
          if (dataObject.getType() == DataObjectType.VOLUME) {
              VolumeInfo volumeInfo = (VolumeInfo) dataObject;
  
              long storagePoolId = dataStore.getId();
              boolean result = false;
              try {
                  result = ElastistorUtil.deleteElastistorVolume(volumeInfo.getUuid());
              } catch (Throwable e) {
                  e.printStackTrace();
                  CommandResult result2 = new CommandResult();
                  result2.setResult(e.toString());
                  callback.complete(result2);
              }
  
              if (result) {
                  long usedBytes = storagePool.getUsedBytes();
                  long capacityIops = storagePool.getCapacityIops();
  
                  usedBytes -= volumeInfo.getSize();
                  capacityIops += volumeInfo.getMaxIops();
  
                  storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
                  storagePool.setCapacityIops(capacityIops < 0 ? 0 : capacityIops);
  
                  _storagePoolDao.update(storagePoolId, storagePool);
              } else {
                  errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
              }
  
          } else {
              errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
          }
  
          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;
      }
  
      @Override
      public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
  
          logger.debug("Resize elastistor volume started");
          Boolean status = false;
          VolumeObject vol = (VolumeObject) data;
          StoragePool pool = (StoragePool) data.getDataStore();
  
          ResizeVolumePayload resizeParameter = (ResizeVolumePayload) vol.getpayload();
  
          CreateCmdResult result = new CreateCmdResult(null, null);
  
          StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
  
          if (!(poolVO.isManaged())) {
              super.resize(data, callback);
              return;
          }
  
          try {
  
              status = ElastistorUtil.updateElastistorVolumeSize(vol.getUuid(), resizeParameter.newSize);
  
          } catch (Throwable e) {
              logger.error("Resize elastistor volume failed, please contact elastistor admin.", e);
              result.setResult(e.toString());
              callback.complete(result);
          }
  
          if (status) {
              // now updating the cloudstack storagepool usedbytes and volume
              Long usedBytes = poolVO.getUsedBytes();
              Long currentVolumeSize = vol.getSize();
              Long newUsedBytes;
  
              if (currentVolumeSize < resizeParameter.newSize) {
                  newUsedBytes = usedBytes + (resizeParameter.newSize - currentVolumeSize);
                  poolVO.setUsedBytes(newUsedBytes);
              } else {
                  newUsedBytes = usedBytes - (currentVolumeSize - resizeParameter.newSize);
                  poolVO.setUsedBytes(newUsedBytes);
              }
  
              _storagePoolDao.update(pool.getId(), poolVO);
  
              vol.getVolume().setSize(resizeParameter.newSize);
              vol.update();
              callback.complete(result);
          } else {
              callback.complete(result);
          }
  
      }
  
      @Override
      public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
  
      //this method will utilize the volume details table to add third party volume properties
      public void updateVolumeDetails(VolumeVO volume, FileSystem esvolume) {
  
          VolumeDetailVO compression = new VolumeDetailVO(volume.getId(), "compression", esvolume.getCompression(), false);
          _volumeDetailsDao.persist(compression);
          VolumeDetailVO deduplication = new VolumeDetailVO(volume.getId(), "deduplication", esvolume.getDeduplication(), false);
          _volumeDetailsDao.persist(deduplication);
          VolumeDetailVO sync = new VolumeDetailVO(volume.getId(), "sync", esvolume.getSync(), false);
          _volumeDetailsDao.persist(sync);
          VolumeDetailVO graceallowed = new VolumeDetailVO(volume.getId(), "graceallowed", esvolume.getGraceallowed(), false);
          _volumeDetailsDao.persist(graceallowed);
  
      }
  
      @Override
      public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) {
          VolumeInfo volume = (VolumeInfo)dataObject;
          long volumeSize = volume.getSize();
          Integer hypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve();
  
          if (hypervisorSnapshotReserve != null) {
              if (hypervisorSnapshotReserve < 25) {
                  hypervisorSnapshotReserve = 25;
              }
  
              volumeSize += volumeSize * (hypervisorSnapshotReserve / 100f);
          }
  
          return volumeSize;
      }
  
      @Override
      public ChapInfo getChapInfo(DataObject dataObject) {
          return null;
      }
  
      @Override
      public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
          CreateCmdResult result = null;
          try {
              logger.info("taking elastistor volume snapshot");
              SnapshotObjectTO snapshotTO = (SnapshotObjectTO)snapshot.getTO();
  
              String volumeid = snapshotTO.getVolume().getUuid();
              String snapshotname = snapshotTO.getName();
  
              Answer answer = ElastistorUtil.createElastistorVolumeSnapshot(volumeid, snapshotname);
  
              if(answer.getResult() == false){
                  logger.info("elastistor volume snapshot failed");
                  throw new CloudRuntimeException("elastistor volume snapshot failed");
              }else{
                  logger.info("elastistor volume snapshot succesfull");
  
                  snapshotTO.setPath(answer.getDetails());
  
                  CreateObjectAnswer createObjectAnswer = new CreateObjectAnswer(snapshotTO);
  
                  result = new CreateCmdResult(null, createObjectAnswer);
  
                  result.setResult(null);
              }
          }
           catch (Throwable e) {
              logger.debug("Failed to take snapshot: " + e.getMessage());
              result = new CreateCmdResult(null, null);
              result.setResult(e.toString());
          }
          callback.complete(result);
      }
  
      @Override
      public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback<CommandResult> callback) {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public Map<String, String> getCapabilities() {
          Map<String, String> mapCapabilities = new HashMap<String, String>();
  
          mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
  
          return mapCapabilities;
      }
  
      @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;
      }
  }
  