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.snapshot;
  
  import java.util.List;
  
  import javax.inject.Inject;
  
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
  import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
  import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
  import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
  import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
  import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
  import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
  import org.apache.cloudstack.storage.to.VolumeObjectTO;
  import org.apache.cloudstack.storage.vmsnapshot.DefaultVMSnapshotStrategy;
  import org.apache.cloudstack.storage.vmsnapshot.VMSnapshotHelper;
  import org.springframework.stereotype.Component;
  
  import com.cloud.agent.api.VMSnapshotTO;
  import com.cloud.event.EventTypes;
  import com.cloud.event.UsageEventUtils;
  import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
  import com.cloud.storage.DiskOfferingVO;
  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.uservm.UserVm;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.fsm.NoTransitionException;
  import com.cloud.vm.UserVmVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.dao.UserVmDao;
  import com.cloud.vm.snapshot.VMSnapshot;
  import com.cloud.vm.snapshot.VMSnapshotDetailsVO;
  import com.cloud.vm.snapshot.VMSnapshotVO;
  import com.cloud.vm.snapshot.dao.VMSnapshotDao;
  import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
  import com.google.gson.JsonArray;
  import com.google.gson.JsonElement;
  import com.google.gson.JsonObject;
  
  @Component
  public class StorPoolVMSnapshotStrategy extends DefaultVMSnapshotStrategy {
  
      @Inject
      private VMSnapshotHelper vmSnapshotHelper;
      @Inject
      private UserVmDao userVmDao;
      @Inject
      private VMSnapshotDao vmSnapshotDao;
      @Inject
      private VolumeDao volumeDao;
      @Inject
      private DiskOfferingDao diskOfferingDao;
      @Inject
      private PrimaryDataStoreDao storagePool;
      @Inject
      private VMSnapshotDetailsDao vmSnapshotDetailsDao;
      @Inject
      private VolumeDataFactory volFactory;
      @Inject
      private VolumeDetailsDao volumeDetailsDao;
      @Inject
      private StoragePoolDetailsDao storagePoolDetailsDao;
      @Inject
      private DataStoreManager dataStoreManager;
      int _wait;
  
      @Override
      public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
          logger.info("KVMVMSnapshotStrategy take snapshot");
          UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());
          VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
  
          try {
              vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.CreateRequested);
          } catch (NoTransitionException e) {
              throw new CloudRuntimeException("No transiontion " + e.getMessage());
          }
  
          boolean result = false;
          try {
  
              List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
              DataStore dataStore = dataStoreManager.getPrimaryDataStore(volumeTOs.get(0).getDataStore().getUuid());
              SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, storagePool);
  
              long prev_chain_size = 0;
              long virtual_size = 0;
              for (VolumeObjectTO volume : volumeTOs) {
                  virtual_size += volume.getSize();
                  VolumeVO volumeVO = volumeDao.findById(volume.getId());
                  prev_chain_size += volumeVO.getVmSnapshotChainSize() == null ? 0 : volumeVO.getVmSnapshotChainSize();
              }
  
              VMSnapshotTO current = null;
              VMSnapshotVO currentSnapshot = vmSnapshotDao.findCurrentSnapshotByVmId(userVm.getId());
              if (currentSnapshot != null) {
                  current = vmSnapshotHelper.getSnapshotWithParents(currentSnapshot);
              }
  
              if (current == null) {
                  vmSnapshotVO.setParent(null);
              } else {
                  vmSnapshotVO.setParent(current.getId());
              }
  
              SpApiResponse resp = StorPoolUtil.volumesGroupSnapshot(volumeTOs, userVm.getUuid(), vmSnapshotVO.getUuid(), "group", conn);
              JsonObject obj = resp.fullJson.getAsJsonObject();
              JsonArray snapshots = obj.getAsJsonObject("data").getAsJsonArray("snapshots");
              StorPoolUtil.spLog("Volumes=%s attached to virtual machine", volumeTOs.toString());
              for (VolumeObjectTO vol : volumeTOs) {
                  for (JsonElement jsonElement : snapshots) {
                      JsonObject snapshotObject = jsonElement.getAsJsonObject();
                      String snapshot = StorPoolUtil
                              .devPath(snapshotObject.getAsJsonPrimitive(StorPoolUtil.GLOBAL_ID).getAsString());
                      if (snapshotObject.getAsJsonPrimitive("volume").getAsString().equals(StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true))
                              || snapshotObject.getAsJsonPrimitive("volumeGlobalId").getAsString().equals(StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), false))) {
                          VMSnapshotDetailsVO vmSnapshotDetailsVO = new VMSnapshotDetailsVO(vmSnapshot.getId(), vol.getUuid(), snapshot, false);
                          vmSnapshotDetailsDao.persist(vmSnapshotDetailsVO);
                          Long poolId = volumeDao.findById(vol.getId()).getPoolId();
                          if (poolId != null) {
                              VMSnapshotDetailsVO vmSnapshotDetailStoragePoolId = new VMSnapshotDetailsVO(
                                      vmSnapshot.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, String.valueOf(poolId), false);
                              vmSnapshotDetailsDao.persist(vmSnapshotDetailStoragePoolId);
                          }
                          StorPoolUtil.spLog("Snapshot=%s of volume=%s for a group snapshot=%s.", snapshot, vol, vmSnapshot);
                      }
                  }
              }
  
              if (resp.getError() == null) {
                  StorPoolUtil.spLog("StorpoolVMSnapshotStrategy.takeSnapshot answer=%s", resp.getError());
                  finalizeCreate(vmSnapshotVO, volumeTOs);
                  result = vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded);
                  long new_chain_size = 0;
                  for (VolumeObjectTO volumeObjectTO : volumeTOs) {
                      publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_CREATE, vmSnapshot, userVm, volumeObjectTO);
                      new_chain_size += volumeObjectTO.getSize();
                      logger.info("EventTypes.EVENT_VM_SNAPSHOT_CREATE publishUsageEvent" + volumeObjectTO);
                  }
                  publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_ON_PRIMARY, vmSnapshot, userVm, new_chain_size - prev_chain_size, virtual_size);
              } else {
                  throw new CloudRuntimeException("Could not create vm snapshot");
              }
              return vmSnapshot;
          } catch (Exception e) {
              logger.debug("Could not create VM snapshot:" + e.getMessage());
              throw new CloudRuntimeException("Could not create VM snapshot:" + e.getMessage());
          } finally {
              if (!result) {
                  try {
                      vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
                      logger.info(String.format("VMSnapshot.Event.OperationFailed vmSnapshot=%s", vmSnapshot));
                  } catch (NoTransitionException nte) {
                      logger.error("Cannot set vm state:" + nte.getMessage());
                  }
              }
          }
      }
  
      @Override
      public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
          return areAllVolumesOnStorPool(vmSnapshot.getVmId());
      }
  
      public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
          if (snapshotMemory) {
              return StrategyPriority.CANT_HANDLE;
          }
          return areAllVolumesOnStorPool(vmId);
      }
  
      private StrategyPriority areAllVolumesOnStorPool(Long vmId) {
          List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmId);
          if (volumeTOs == null || volumeTOs.isEmpty()) {
              return StrategyPriority.CANT_HANDLE;
          }
          for (VolumeObjectTO volumeTO : volumeTOs) {
              Long poolId = volumeTO.getPoolId();
              StoragePoolVO pool = storagePool.findById(poolId);
              if (!pool.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) {
                  return StrategyPriority.CANT_HANDLE;
              }
          }
          return StrategyPriority.HIGHEST;
      }
  
      @Override
      public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) {
          UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
          VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
          try {
              vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.ExpungeRequested);
          } catch (NoTransitionException e) {
              logger.debug("Failed to change vm snapshot state with event ExpungeRequested");
              throw new CloudRuntimeException(
                      "Failed to change vm snapshot state with event ExpungeRequested: " + e.getMessage());
          }
  
          List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
          DataStore dataStore = dataStoreManager.getPrimaryDataStore(volumeTOs.get(0).getDataStore().getUuid());
          SpConnectionDesc conn = null;
          try {
              conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, storagePool);
          } catch (CloudRuntimeException e) {
              throw e;
          }
  
          SpApiResponse resp = null;
          for (VolumeObjectTO volumeObjectTO : volumeTOs) {
              String err = null;
              VMSnapshotDetailsVO snapshotDetailsVO = vmSnapshotDetailsDao.findDetail(vmSnapshot.getId(), volumeObjectTO.getUuid());
              String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshotDetailsVO.getValue(), true);
              if (snapshotName == null) {
                  err = String.format("Could not find StorPool's snapshot vm snapshot %s and volume [id: %s, uuid: %s, name: %s]",
                          vmSnapshot, volumeObjectTO.getId(), volumeObjectTO.getUuid(), volumeObjectTO.getName());
                  logger.error("Could not delete snapshot for vm:" + err);
              }
              StorPoolUtil.spLog("StorpoolVMSnapshotStrategy.deleteVMSnapshot snapshotName=%s", snapshotName);
              resp = StorPoolUtil.snapshotDelete(snapshotName, conn);
              if (resp.getError() != null) {
                  err = String.format("Could not delete storpool vm error=%s", resp.getError());
                  logger.error("Could not delete snapshot for vm:" + err);
              } else {
                  // do we need to clean database?
                  if (snapshotDetailsVO != null) {
                      vmSnapshotDetailsDao.remove(snapshotDetailsVO.getId());
                  }
              }
              if (err != null) {
                  StorPoolUtil.spLog(
                          "StorpoolVMSnapshotStrategy.deleteVMSnapshot delete snapshot=%s of group snapshot=%s failed due to %s",
                          snapshotName, userVm, err);
                  throw new CloudRuntimeException(String.format("Delete vm snapshot %s of vm %s failed due to %s", vmSnapshot, userVm, err));
              }
          }
          vmSnapshotDetailsDao.removeDetails(vmSnapshot.getId());
  
          finalizeDelete(vmSnapshotVO, volumeTOs);
          vmSnapshotDao.remove(vmSnapshot.getId());
  
          long full_chain_size = 0;
          for (VolumeObjectTO volumeTo : volumeTOs) {
              publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo);
              full_chain_size += volumeTo.getSize();
          }
          publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L);
          return true;
      }
  
      @Override
      public boolean revertVMSnapshot(VMSnapshot vmSnapshot) {
          logger.debug("Revert vm snapshot");
          VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
          UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
  
          if (userVm.getState() == VirtualMachine.State.Running && vmSnapshotVO.getType() == VMSnapshot.Type.Disk) {
              throw new CloudRuntimeException("Virtual machine should be in stopped state for revert operation");
          }
  
          try {
              vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.RevertRequested);
          } catch (NoTransitionException e) {
              throw new CloudRuntimeException(e.getMessage());
          }
  
          boolean result = false;
          try {
              List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
  
              DataStore dataStore = dataStoreManager.getPrimaryDataStore(volumeTOs.get(0).getDataStore().getUuid());
              SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, storagePool);
              for (VolumeObjectTO volumeObjectTO : volumeTOs) {
                  String err = null;
                  VMSnapshotDetailsVO snapshotDetailsVO = vmSnapshotDetailsDao.findDetail(vmSnapshot.getId(),
                          volumeObjectTO.getUuid());
                  String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshotDetailsVO.getValue(), true);
                  if (snapshotName == null) {
                      err = String.format("Could not find StorPool's snapshot vm snapshot uuid=%s and volume uui=%s",
                              vmSnapshot.getUuid(), volumeObjectTO.getUuid());
                      logger.error("Could not delete snapshot for vm:" + err);
                  }
                  String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(volumeObjectTO.getPath(), true);
                  VolumeDetailVO detail = volumeDetailsDao.findDetail(volumeObjectTO.getId(), StorPoolUtil.SP_PROVIDER_NAME);
                  if (detail != null) {
                      SpApiResponse updateVolumeResponse = StorPoolUtil.volumeUpdateRename(volumeName, "", StorPoolStorageAdaptor.getVolumeNameFromPath(detail.getValue(), false), conn);
  
                      if (updateVolumeResponse.getError() != null) {
                          StorPoolUtil.spLog("StorpoolVMSnapshotStrategy.canHandle - Could not update StorPool's volume %s to it's globalId due to %s", volumeName, updateVolumeResponse.getError().getDescr());
                          err = String.format("StorpoolVMSnapshotStrategy.canHandle - Could not update StorPool's volume %s to it's globalId due to %s", volumeName, updateVolumeResponse.getError().getDescr());
                      } else {
                          volumeDetailsDao.remove(detail.getId());
                      }
                  }
  
                  SpApiResponse resp = StorPoolUtil.detachAllForced(volumeName, false, conn);
                  if (resp.getError() != null) {
                      err = String.format("Could not detach StorPool volume %s from a group snapshot, due to %s",
                              volumeName, resp.getError());
                      throw new CloudRuntimeException(err);
                  }
                  resp = StorPoolUtil.volumeRevert(volumeName, snapshotName, conn);
                  if (resp.getError() != null) {
                      err = String.format("Create Could not complete revert task for volumeName=%s , and snapshotName=%s",
                              volumeName, snapshotName);
                      throw new CloudRuntimeException(err);
                  }
                  VolumeInfo vinfo = volFactory.getVolume(volumeObjectTO.getId());
                  if (vinfo.getMaxIops() != null) {
                      resp = StorPoolUtil.volumeUpdateIopsAndTags(volumeName, null, vinfo.getMaxIops(), conn, null);
  
                      if (resp.getError() != null) {
                          StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s",
                                  resp.getError().getDescr());
                      }
                  }
              }
              finalizeRevert(vmSnapshotVO, volumeTOs);
              result = vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded);
          } catch (CloudRuntimeException | NoTransitionException  e) {
              String errMsg = String.format("Error while finalize create vm snapshot [%s] due to %s", vmSnapshot, e.getMessage());
              logger.error(errMsg, e);
              throw new CloudRuntimeException(errMsg);
          } finally {
              if (!result) {
                  try {
                      vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
                  } catch (NoTransitionException e1) {
                      logger.error("Cannot set vm snapshot state due to: " + e1.getMessage());
                  }
              }
          }
          return result;
      }
  
      private void publishUsageEvents(String type, VMSnapshot vmSnapshot, UserVm userVm, VolumeObjectTO volumeTo) {
          VolumeVO volume = volumeDao.findById(volumeTo.getId());
          Long diskOfferingId = volume.getDiskOfferingId();
          Long offeringId = null;
          if (diskOfferingId != null) {
              DiskOfferingVO offering = diskOfferingDao.findById(diskOfferingId);
              if (offering != null && offering.isComputeOnly()) {
                  offeringId = offering.getId();
              }
          }
          UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(),
                  vmSnapshot.getName(), offeringId, volume.getId(), volumeTo.getSize(), VMSnapshot.class.getName(), vmSnapshot.getUuid());
      }
  
      private void publishUsageEvents(String type, VMSnapshot vmSnapshot, UserVm userVm, Long vmSnapSize, Long virtualSize) {
          try {
              UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(),
                      vmSnapshot.getName(), 0L, 0L, vmSnapSize, virtualSize, VMSnapshot.class.getName(),
                      vmSnapshot.getUuid());
          } catch (Exception e) {
              logger.error("Failed to publis usage event " + type, e);
          }
      }
  }
  