/*
 * 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.vmsnapshot;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.naming.ConfigurationException;

import com.cloud.storage.StoragePool;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotStrategy;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.datastore.api.SnapshotGroup;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
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.ScaleIOUtil;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.collections.CollectionUtils;

import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.alert.AlertManager;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.event.UsageEventVO;
import com.cloud.server.ManagementServerImpl;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.UserVmVO;
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;

public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshotStrategy {
    @Inject
    VMSnapshotHelper vmSnapshotHelper;
    @Inject
    UserVmDao userVmDao;
    @Inject
    VMSnapshotDao vmSnapshotDao;
    @Inject
    protected VMSnapshotDetailsDao vmSnapshotDetailsDao;
    int _wait;
    @Inject
    ConfigurationDao configurationDao;
    @Inject
    VolumeDao volumeDao;
    @Inject
    DiskOfferingDao diskOfferingDao;
    @Inject
    PrimaryDataStoreDao storagePoolDao;
    @Inject
    StoragePoolDetailsDao storagePoolDetailsDao;
    @Inject
    AlertManager alertManager;

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        String value = configurationDao.getValue("vmsnapshot.create.wait");
        _wait = NumbersUtil.parseInt(value, 1800);
        return true;
    }

    @Override
    public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
        List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
        if (volumeTOs == null) {
            throw new CloudRuntimeException("Failed to get the volumes for the vm snapshot: " + vmSnapshot.getUuid());
        }

        if (!VMSnapshot.State.Allocated.equals(vmSnapshot.getState())) {
            List<VMSnapshotDetailsVO> vmDetails = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), "SnapshotGroupId" );
            if (CollectionUtils.isEmpty(vmDetails)) {
                return StrategyPriority.CANT_HANDLE;
            }
        }

        if (volumeTOs != null && !volumeTOs.isEmpty()) {
            for (VolumeObjectTO volumeTO: volumeTOs) {
                Long poolId  = volumeTO.getPoolId();
                Storage.StoragePoolType poolType = vmSnapshotHelper.getStoragePoolType(poolId);
                if (poolType != Storage.StoragePoolType.PowerFlex) {
                    return StrategyPriority.CANT_HANDLE;
                }
            }
        }

        return StrategyPriority.HIGHEST;
    }

    @Override
    public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
        if (snapshotMemory) {
            return StrategyPriority.CANT_HANDLE;
        }
        List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmId);
        if (volumeTOs == null || volumeTOs.isEmpty()) {
            return StrategyPriority.CANT_HANDLE;
        }

        for (VolumeObjectTO volumeTO : volumeTOs) {
            Long poolId = volumeTO.getPoolId();
            Storage.StoragePoolType poolType = vmSnapshotHelper.getStoragePoolType(poolId);
            if (poolType != Storage.StoragePoolType.PowerFlex || volumeTO.getFormat() != ImageFormat.RAW || poolId != rootPoolId) {
                return StrategyPriority.CANT_HANDLE;
            }
        }

        return StrategyPriority.HIGHEST;
    }

    @Override
    public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
        UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());
        VMSnapshotVO vmSnapshotVO = (VMSnapshotVO)vmSnapshot;

        try {
            vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.CreateRequested);
        } catch (NoTransitionException e) {
            throw new CloudRuntimeException(e.getMessage());
        }

        boolean result = false;
        try {
            Map<String, String> srcVolumeDestSnapshotMap = new HashMap<>();
            List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());

            StoragePoolVO storagePool = vmSnapshotHelper.getStoragePoolForVM(userVm);
            long prev_chain_size = 0;
            long virtual_size=0;
            for (VolumeObjectTO volume : volumeTOs) {
                String volumeSnapshotName = String.format("%s-%s-%s-%s-%s", ScaleIOUtil.VMSNAPSHOT_PREFIX, vmSnapshotVO.getId(), volume.getId(),
                        storagePool.getUuid().split("-")[0].substring(4), ManagementServerImpl.customCsIdentifier.value());
                srcVolumeDestSnapshotMap.put(ScaleIOUtil.getVolumePath(volume.getPath()), volumeSnapshotName);

                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());

            try {
                final ScaleIOGatewayClient client = getScaleIOClient(storagePool);
                SnapshotGroup snapshotGroup = client.takeSnapshot(srcVolumeDestSnapshotMap);
                if (snapshotGroup == null) {
                    throw new CloudRuntimeException("Failed to take VM snapshot on PowerFlex storage pool");
                }

                String snapshotGroupId = snapshotGroup.getSnapshotGroupId();
                List<String> volumeIds = snapshotGroup.getVolumeIds();
                if (volumeIds != null && !volumeIds.isEmpty()) {
                    List<VMSnapshotDetailsVO> vmSnapshotDetails = new ArrayList<VMSnapshotDetailsVO>();
                    vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "SnapshotGroupId", snapshotGroupId, false));

                    for (int index = 0; index < volumeIds.size(); index++) {
                        String volumeSnapshotName = srcVolumeDestSnapshotMap.get(ScaleIOUtil.getVolumePath(volumeTOs.get(index).getPath()));
                        String pathWithScaleIOVolumeName = ScaleIOUtil.updatedPathWithVolumeName(volumeIds.get(index), volumeSnapshotName);
                        vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "Vol_" + volumeTOs.get(index).getId() + "_Snapshot", pathWithScaleIOVolumeName, false));
                    }

                    vmSnapshotDetailsDao.saveDetails(vmSnapshotDetails);
                }

                finalizeCreate(vmSnapshotVO, volumeTOs);
                result = true;
                logger.debug("Create vm snapshot " + vmSnapshot.getName() + " succeeded for vm: " + userVm.getInstanceName());

                long new_chain_size=0;
                for (VolumeObjectTO volumeTo : volumeTOs) {
                    publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_CREATE, vmSnapshot, userVm, volumeTo);
                    new_chain_size += volumeTo.getSize();
                }
                publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_ON_PRIMARY, vmSnapshot, userVm, new_chain_size - prev_chain_size, virtual_size);
                return vmSnapshot;
            } catch (Exception e) {
                String errMsg = "Unable to take vm snapshot due to: " + e.getMessage();
                logger.warn(errMsg, e);
                throw new CloudRuntimeException(errMsg);
            }
        } finally {
            if (!result) {
                try {
                    vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);

                    String subject = "Take snapshot failed for VM: " + userVm.getDisplayName();
                    String message = "Snapshot operation failed for VM: " + userVm.getDisplayName() + ", Please check and delete if any stale volumes created with VM snapshot id: " + vmSnapshot.getVmId();
                    alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_VM_SNAPSHOT, userVm.getDataCenterId(), userVm.getPodIdToDeployIn(), subject, message);
                } catch (NoTransitionException e1) {
                    logger.error("Cannot set vm snapshot state due to: " + e1.getMessage());
                }
            }
        }
    }

    @DB
    protected void finalizeCreate(VMSnapshotVO vmSnapshot, List<VolumeObjectTO> volumeTOs) {
        try {
            Transaction.execute(new TransactionCallbackWithExceptionNoReturn<NoTransitionException>() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) throws NoTransitionException {
                    // update chain size for the volumes in the VM snapshot
                    for (VolumeObjectTO volume : volumeTOs) {
                        VolumeVO volumeVO = volumeDao.findById(volume.getId());
                        if (volumeVO != null) {
                            long vmSnapshotChainSize = volumeVO.getVmSnapshotChainSize() == null ? 0 : volumeVO.getVmSnapshotChainSize();
                            vmSnapshotChainSize += volumeVO.getSize();
                            volumeVO.setVmSnapshotChainSize(vmSnapshotChainSize);
                            volumeDao.persist(volumeVO);
                        }
                    }

                    vmSnapshot.setCurrent(true);

                    // change current snapshot
                    if (vmSnapshot.getParent() != null) {
                        VMSnapshotVO previousCurrent = vmSnapshotDao.findById(vmSnapshot.getParent());
                        previousCurrent.setCurrent(false);
                        vmSnapshotDao.persist(previousCurrent);
                    }
                    vmSnapshotDao.persist(vmSnapshot);

                    vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded);
                }
            });
        } catch (Exception e) {
            String errMsg = "Error while finalize create vm snapshot: " + vmSnapshot.getName() + " due to " + e.getMessage();
            logger.error(errMsg, e);
            throw new CloudRuntimeException(errMsg);
        }
    }

    @Override
    public boolean revertVMSnapshot(VMSnapshot vmSnapshot) {
        VMSnapshotVO vmSnapshotVO = (VMSnapshotVO)vmSnapshot;
        UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());

        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());
            StoragePoolVO storagePool = vmSnapshotHelper.getStoragePoolForVM(userVm);
            Long storagePoolId = storagePool.getId();
            Map<String, String> srcSnapshotDestVolumeMap = new HashMap<>();
            for (VolumeObjectTO volume : volumeTOs) {
                VMSnapshotDetailsVO vmSnapshotDetail = vmSnapshotDetailsDao.findDetail(vmSnapshotVO.getId(), "Vol_" + volume.getId() + "_Snapshot");
                String srcSnapshotVolumeId = ScaleIOUtil.getVolumePath(vmSnapshotDetail.getValue());
                String destVolumeId = ScaleIOUtil.getVolumePath(volume.getPath());
                srcSnapshotDestVolumeMap.put(srcSnapshotVolumeId, destVolumeId);
            }

            String systemId = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
            if (systemId == null) {
                throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool for reverting VM snapshot: " + vmSnapshot.getName());
            }

            final ScaleIOGatewayClient client = getScaleIOClient(storagePool);
            result = client.revertSnapshot(systemId, srcSnapshotDestVolumeMap);
            if (!result) {
                throw new CloudRuntimeException("Failed to revert VM snapshot on PowerFlex storage pool");
            }

            finalizeRevert(vmSnapshotVO, volumeTOs);
            result = true;
        } catch (Exception e) {
            String errMsg = String.format("Revert VM: %s to snapshot: %s failed due to %s", userVm, vmSnapshotVO, 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;
    }

    @DB
    protected void finalizeRevert(VMSnapshotVO vmSnapshot, List<VolumeObjectTO> volumeToList) {
        try {
            Transaction.execute(new TransactionCallbackWithExceptionNoReturn<NoTransitionException>() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) throws NoTransitionException {
                    // update chain size for the volumes in the VM snapshot
                    for (VolumeObjectTO volume : volumeToList) {
                        VolumeVO volumeVO = volumeDao.findById(volume.getId());
                        if (volumeVO != null && volumeVO.getVmSnapshotChainSize() != null && volumeVO.getVmSnapshotChainSize() >= volumeVO.getSize()) {
                            long vmSnapshotChainSize = volumeVO.getVmSnapshotChainSize() - volumeVO.getSize();
                            volumeVO.setVmSnapshotChainSize(vmSnapshotChainSize);
                            volumeDao.persist(volumeVO);
                        }
                    }

                    // update current snapshot, current snapshot is the one reverted to
                    VMSnapshotVO previousCurrent = vmSnapshotDao.findCurrentSnapshotByVmId(vmSnapshot.getVmId());
                    if (previousCurrent != null) {
                        previousCurrent.setCurrent(false);
                        vmSnapshotDao.persist(previousCurrent);
                    }
                    vmSnapshot.setCurrent(true);
                    vmSnapshotDao.persist(vmSnapshot);

                    vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded);
                }
            });
        } catch (Exception e) {
            String errMsg = "Error while finalize revert vm snapshot: " + vmSnapshot.getName() + " due to " + e.getMessage();
            logger.error(errMsg, e);
            throw new CloudRuntimeException(errMsg);
        }
    }

    @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());
        }

        try {
            List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
            StoragePoolVO storagePool = vmSnapshotHelper.getStoragePoolForVM(userVm);
            String systemId = storagePoolDetailsDao.findDetail(storagePool.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
            if (systemId == null) {
                throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool for deleting VM snapshot: " + vmSnapshot.getName());
            }

            VMSnapshotDetailsVO vmSnapshotDetailsVO = vmSnapshotDetailsDao.findDetail(vmSnapshot.getId(), "SnapshotGroupId");
            if (vmSnapshotDetailsVO == null) {
                throw new CloudRuntimeException("Failed to get snapshot group id for the VM snapshot: " + vmSnapshot.getName());
            }

            String snapshotGroupId = vmSnapshotDetailsVO.getValue();
            final ScaleIOGatewayClient client = getScaleIOClient(storagePool);
            int volumesDeleted = client.deleteSnapshotGroup(systemId, snapshotGroupId);
            if (volumesDeleted <= 0) {
                throw new CloudRuntimeException("Failed to delete VM snapshot: " + vmSnapshot.getName());
            } else if (volumesDeleted != volumeTOs.size()) {
                logger.warn("Unable to delete all volumes of the VM snapshot: " + vmSnapshot.getName());
            }

            finalizeDelete(vmSnapshotVO, volumeTOs);
            long full_chain_size=0;
            for (VolumeObjectTO volumeTo : volumeTOs) {
                publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo);
                full_chain_size += volumeTo.getSize();
            }
            publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L);
            return true;
        } catch (Exception e) {
            String errMsg = "Unable to delete vm snapshot: " + vmSnapshot.getName() + " of vm " + userVm.getInstanceName() + " due to " + e.getMessage();
            logger.warn(errMsg, e);
            throw new CloudRuntimeException(errMsg);
        }
    }

    @DB
    protected void finalizeDelete(VMSnapshotVO vmSnapshot, List<VolumeObjectTO> volumeTOs) {
        try {
            Transaction.execute(new TransactionCallbackWithExceptionNoReturn<NoTransitionException>() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) throws NoTransitionException {
                    // update chain size for the volumes in the VM snapshot
                    for (VolumeObjectTO volume : volumeTOs) {
                        VolumeVO volumeVO = volumeDao.findById(volume.getId());
                        if (volumeVO != null && volumeVO.getVmSnapshotChainSize() != null && volumeVO.getVmSnapshotChainSize() >= volumeVO.getSize()) {
                            long vmSnapshotChainSize = volumeVO.getVmSnapshotChainSize() - volumeVO.getSize();
                            volumeVO.setVmSnapshotChainSize(vmSnapshotChainSize);
                            volumeDao.persist(volumeVO);
                        }
                    }

                    // update children's parent snapshots
                    List<VMSnapshotVO> children = vmSnapshotDao.listByParent(vmSnapshot.getId());
                    for (VMSnapshotVO child : children) {
                        child.setParent(vmSnapshot.getParent());
                        vmSnapshotDao.persist(child);
                    }

                    // update current snapshot
                    VMSnapshotVO current = vmSnapshotDao.findCurrentSnapshotByVmId(vmSnapshot.getVmId());
                    if (current != null && current.getId() == vmSnapshot.getId() && vmSnapshot.getParent() != null) {
                        VMSnapshotVO parent = vmSnapshotDao.findById(vmSnapshot.getParent());
                        parent.setCurrent(true);
                        vmSnapshotDao.persist(parent);
                    }
                    vmSnapshot.setCurrent(false);
                    vmSnapshotDao.persist(vmSnapshot);

                    vmSnapshotDao.remove(vmSnapshot.getId());
                }
            });
        } catch (Exception e) {
            String errMsg = "Error while finalize delete vm snapshot: " + vmSnapshot.getName() + " due to " + e.getMessage();
            logger.error(errMsg, e);
            throw new CloudRuntimeException(errMsg);
        }
    }

    @Override
    public boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot, boolean unmanage) {
        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());
        }
        UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());
        List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
        long full_chain_size = 0;
        for (VolumeObjectTO volumeTo: volumeTOs) {
            volumeTo.setSize(0);
            publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo);
            full_chain_size += volumeTo.getSize();
        }
        if (unmanage) {
            publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L);
        }
        return vmSnapshotDao.remove(vmSnapshot.getId());
    }

    @Override
    public void updateOperationFailed(VMSnapshot vmSnapshot) throws NoTransitionException {
        try {
            vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
        } catch (NoTransitionException e) {
            logger.debug("Failed to change vm snapshot state with event OperationFailed");
            throw e;
        }
    }

    private void publishUsageEvent(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();
            }
        }
        Map<String, String> details = new HashMap<>();
        if (vmSnapshot != null) {
            details.put(UsageEventVO.DynamicParameters.vmSnapshotId.name(), String.valueOf(vmSnapshot.getId()));
        }
        UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(), vmSnapshot.getName(), offeringId, volume.getId(), // save volume's id into templateId field
                volumeTo.getSize(), VMSnapshot.class.getName(), vmSnapshot.getUuid(), details);
    }

    private void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm, Long vmSnapSize, Long virtualSize) {
        try {
            Map<String, String> details = new HashMap<>();
            if (vmSnapshot != null) {
                details.put(UsageEventVO.DynamicParameters.vmSnapshotId.name(), String.valueOf(vmSnapshot.getId()));
            }
            UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(), vmSnapshot.getName(), 0L, 0L, vmSnapSize, virtualSize,
                    VMSnapshot.class.getName(), vmSnapshot.getUuid(), details);
        } catch (Exception e) {
            logger.error("Failed to publish usage event " + type, e);
        }
    }

    private ScaleIOGatewayClient getScaleIOClient(final StoragePool storagePool) throws Exception {
        return ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePool, storagePoolDetailsDao);
    }
}
