com.eucalyptus.blockstorage.ceph.CephRbdProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.blockstorage.ceph.CephRbdProvider.java

Source

/*************************************************************************
 * (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 *  This file may incorporate work covered under the following copyright and
 *  permission notice:
 *
 *    Software License Agreement (BSD License)
 *
 *    Copyright (c) 2008, Regents of the University of California
 *    All rights reserved.
 *
 *    Redistribution and use of this software in source and binary forms, with
 *    or without modification, are permitted provided that the following
 *    conditions are met:
 *
 *      Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *
 *      Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 *    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 *    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 *    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF
 *    THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE
 *    LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS
 *    SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *    IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA
 *    BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN
 *    THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
 *    OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR
 *    WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH
 *    ANY SUCH LICENSES OR RIGHTS.
 *******************************************************************************/

package com.eucalyptus.blockstorage.ceph;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.ceph.rbd.Rbd;

import com.eucalyptus.blockstorage.FileResource;
import com.eucalyptus.blockstorage.SnapPointsUpdater;
import com.eucalyptus.blockstorage.StorageManagers.StorageManagerProperty;
import com.eucalyptus.blockstorage.StorageResource;
import com.eucalyptus.blockstorage.ceph.entities.CephRbdImageToBeDeleted;
import com.eucalyptus.blockstorage.ceph.entities.CephRbdInfo;
import com.eucalyptus.blockstorage.ceph.entities.CephRbdSnapshotToBeDeleted;
import com.eucalyptus.blockstorage.ceph.entities.CephRbdSnapshotToBeDeleted_;
import com.eucalyptus.blockstorage.entities.SnapshotInfo;
import com.eucalyptus.blockstorage.entities.SnapshotInfo_;
import com.eucalyptus.blockstorage.san.common.SANManager;
import com.eucalyptus.blockstorage.san.common.SANProvider;
import com.eucalyptus.blockstorage.util.StorageProperties;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.storage.common.CheckerTask;
import com.eucalyptus.util.EucalyptusCloudException;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

import edu.ucsb.eucalyptus.msgs.ComponentProperty;
import edu.ucsb.eucalyptus.util.SystemUtil;
import edu.ucsb.eucalyptus.util.SystemUtil.CommandOutput;
import edu.ucsb.eucalyptus.util.EucaSemaphore;
import edu.ucsb.eucalyptus.util.EucaSemaphoreDirectory;

/**
 * CephProvider implements the Eucalyptus Storage Controller plug-in for interacting with a Ceph cluster
 * 
 */
@StorageManagerProperty(value = "ceph-rbd", manager = SANManager.class)
public class CephRbdProvider implements SANProvider {

    private static final Logger LOG = Logger.getLogger(CephRbdProvider.class);
    private static final Splitter COMMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();
    private static Set<String> accessiblePools;
    private static final String SEMAPHORE_PREFIX = "snapshot point volume ";
    private static final Function<CephRbdImageToBeDeleted, String> IMAGE_NAME_FUNCTION = new Function<CephRbdImageToBeDeleted, String>() {
        @Override
        public String apply(CephRbdImageToBeDeleted arg0) {
            return arg0.getImageName();
        }
    };

    private CephRbdAdapter rbdService;
    private CephRbdInfo cachedConfig;

    @Override
    public void initialize() {
        // Create and persist ceph info entity if its not already there
        LOG.info("Initializing CephInfo entity");
        CephRbdInfo.getStorageInfo();
    }

    @Override
    public void configure() throws EucalyptusCloudException {
        CephRbdInfo cephInfo = CephRbdInfo.getStorageInfo();
        initializeRbdService(cephInfo);
    }

    private void initializeRbdService(CephRbdInfo info) {
        LOG.info("Initializing Ceph RBD service provider");

        cachedConfig = info;

        if (rbdService == null) {
            rbdService = new CephRbdFormatTwoAdapter(cachedConfig);
        } else {
            // Changing the configuration in the existing reference rather than instantiating a new object as that might end up interrupting an already
            // existing operation
            rbdService.setCephConfig(cachedConfig);
        }

        // TODO Some way to check connectivity to ceph cluster

        accessiblePools = Sets.newHashSet();
        accessiblePools.addAll(COMMA_SPLITTER.splitToList(cachedConfig.getCephVolumePools()));
        accessiblePools.addAll(COMMA_SPLITTER.splitToList(cachedConfig.getCephSnapshotPools()));

        // For Euca v4.4.x only, check for missing snapshot points and attempt to fill them in.
        //TODO To be removed in v5.0
        SnapPointsUpdater.updateSnapPoints();
    }

    @Override
    public void checkConnection() throws EucalyptusCloudException {
        CephRbdInfo info = CephRbdInfo.getStorageInfo();
        if (info != null && !cachedConfig.isSame(info)) {
            LOG.info("Detected a change in Ceph configuration");
            initializeRbdService(info);
        } else {
            // Nothing to do here
        }
    }

    @Override
    public String createVolume(String volumeId, String snapshotId, int snapSize, int size, String snapshotIqn)
            throws EucalyptusCloudException {
        LOG.info("Creating volume volumeId=" + volumeId + ", snapshotId=" + snapshotId + ", size=" + size
                + "GB, snapshotIqn=" + snapshotIqn);

        CanonicalRbdObject parent = CanonicalRbdObject.parse(snapshotIqn);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool())) {
            String iqn = null;
            if (size > snapSize) {
                iqn = rbdService.cloneAndResizeImage(snapshotId, CephRbdInfo.SNAPSHOT_ON_PREFIX + snapshotId,
                        volumeId, Long.valueOf(size * StorageProperties.GB), parent.getPool());
            } else {
                iqn = rbdService.cloneAndResizeImage(snapshotId, CephRbdInfo.SNAPSHOT_ON_PREFIX + snapshotId,
                        volumeId, null, parent.getPool());
            }
            return iqn;
        } else {
            LOG.warn("Expected snapshotIqn format: pool/image, actual snapshotIqn: " + snapshotIqn);
            throw new EucalyptusCloudException("Failed to create volume " + volumeId
                    + ". Expected snapshotIqn format: pool/image, actual snapshotIqn: " + snapshotIqn);

        }
    }

    @Override
    public String cloneVolume(String volumeId, String parentVolumeId, String parentVolumeIqn)
            throws EucalyptusCloudException {
        LOG.info("Cloning volume volumeId=" + volumeId + ", parentVolumeId=" + parentVolumeId + ", parentVolumeIqn="
                + parentVolumeIqn);

        CanonicalRbdObject parent = CanonicalRbdObject.parse(parentVolumeIqn);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool())) {
            String snapshotPoint = CephRbdInfo.SNAPSHOT_ON_PREFIX + parentVolumeId;
            String iqn = rbdService.cloneAndResizeImage(parentVolumeId, snapshotPoint, volumeId, null,
                    parent.getPool());
            return iqn;
        } else {
            LOG.warn("Expected snapshotIqn format: pool/image, actual parentVolumeIqn: " + parentVolumeIqn);
            throw new EucalyptusCloudException("Failed to create volume " + volumeId
                    + ". Expected snapshotIqn format: pool/image, actual parentVolumeIqn: " + parentVolumeIqn);

        }
    }

    @Override
    public StorageResource connectTarget(String iqn, String lun) throws EucalyptusCloudException {
        LOG.debug("Connecting iqn=" + iqn + ", lun=" + lun + ". This is a no-op");
        // iqn and lun are be the same, use one of them
        // SANManager changes the ID, so dont bother setting the volume ID (first parameter) here
        LOG.trace("Returning CephRbdResource initialized with inbound argument lun=" + lun);
        return new CephRbdResource(lun, lun);
    }

    @Override
    public String getVolumeConnectionString(String volumeId) {
        LOG.debug("Getting volume connection string volumeId=" + volumeId);
        return CephRbdInfo.getStorageInfo().getVirshSecret() + ",,,"; // <virsh secret uuid>,<empty path>
    }

    @Override
    public String createVolume(String volumeName, int size) throws EucalyptusCloudException {
        LOG.info("Creating volume volumeId=" + volumeName + ", size=" + size + "GB");
        long sizeInBytes = size * StorageProperties.GB; // need to go from gb to bytes
        String iqn = rbdService.createImage(volumeName, sizeInBytes);
        return iqn;
    }

    @Override
    public boolean deleteVolume(String volumeId, String volumeIqn) {
        LOG.info("Deleting volume volumeId=" + volumeId + ", volumeIqn=" + volumeIqn);

        boolean result = false;
        try {
            // volumeIqn is of the form pool/image, get the pool information
            CanonicalRbdObject can = CanonicalRbdObject.parse(volumeIqn);

            // Add images to be removed to the database, duty cycles will clean them up and update the database
            Transactions.save(new CephRbdImageToBeDeleted(volumeId, (can != null ? can.getPool() : null)));
            EucaSemaphoreDirectory.removeSemaphore(SEMAPHORE_PREFIX + volumeId);
            result = true;
        } catch (Exception e) {
            LOG.warn("Failed to save metadata for asynchronous deletion of " + volumeId);
        }

        return result;
    }

    @Override
    public boolean deleteSnapshot(String snapshotId, String snapshotIqn, String snapshotPointId) {
        LOG.info("Deleting snapshot snapshotId=" + snapshotId + ", snapshotIqn=" + snapshotIqn
                + ", snapshotPointId=" + snapshotPointId);
        try {
            // Deleting EBS snapshots involves
            // 1. Deleting RBD image mapped to the EBS snapshot
            // 2. Deleting RBD snapshot on the image mapped to the EBS volume against which the EBS snapshot was created

            // Parse snapshotIqn for information about RBD image. snapshotIqn is of the form pool/image
            CanonicalRbdObject snapImage = CanonicalRbdObject.parse(snapshotIqn);

            // Add it to database, duty cycles will clean them up and update the database
            Transactions.save(
                    new CephRbdImageToBeDeleted(snapshotId, (snapImage != null ? snapImage.getPool() : null)));

            // Parse snapshotPointId for information about RBD snapshot. snapshotPointId is of the form pool/image@snapshot
            CanonicalRbdObject snapParent = CanonicalRbdObject.parse(snapshotPointId);
            if (snapParent != null && !Strings.isNullOrEmpty(snapParent.getSnapshot())
                    && !Strings.isNullOrEmpty(snapParent.getPool())
                    && !Strings.isNullOrEmpty(snapParent.getImage())) {
                // Add it to database, duty cycles will clean them up and update the database
                Transactions.save(new CephRbdSnapshotToBeDeleted(snapParent.getPool(), snapParent.getImage(),
                        snapParent.getSnapshot()));
            } else {
                // If the snapshot was created before Euca v4.4.0, then there would be
                // no snapshot point ID, but the PopulateSnapPoints groovy script 
                // (existing only in v4.4.x) should have found it and filled in the
                // snapshot point ID during Ceph provider initialization.
                LOG.debug("Cannot delete RBD snapshot for " + snapshotId + " due to an invalid snapshot point ID "
                        + snapshotPointId + ". If this EBS snapshot originated in another AZ, then this is normal. "
                        + "Otherwise, you may have to delete the Ceph RBD snapshot manually.");
            }

            return true;
        } catch (Exception e) {
            LOG.warn("Failed to save metadata for asynchronous deletion of " + snapshotId);
        }

        return false;
    }

    @Override
    public String createSnapshot(String volumeId, String snapshotId, String snapshotPointId)
            throws EucalyptusCloudException {
        LOG.info("Creating snapshot snapshotId=" + snapshotId + ", volumeId=" + volumeId + ", snapshotPointId="
                + snapshotPointId);

        // snapshotPointId is of the form pool/image@snapshot
        CanonicalRbdObject parent = CanonicalRbdObject.parse(snapshotPointId);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool()) && !Strings.isNullOrEmpty(parent.getImage())
                && !Strings.isNullOrEmpty(parent.getSnapshot())) {
            String iqn = rbdService.cloneAndResizeImage(parent.getImage(), parent.getSnapshot(), snapshotId, null,
                    parent.getPool());
            return iqn;
        } else {
            LOG.warn("Expected snapshotPointId format: pool/image@snapshot, actual snapshotPointId: "
                    + snapshotPointId);
            throw new EucalyptusCloudException("Failed to create " + snapshotId
                    + ". Expected snapshotPointId format: pool/image@snapshot, actual snapshotPointId: "
                    + snapshotPointId);
        }
    }

    @Override
    public void deleteUser(String userName) throws EucalyptusCloudException {

    }

    @Override
    public void addUser(String userName) throws EucalyptusCloudException {

    }

    @Override
    public void disconnectTarget(String snapshotId, String iqn, String lun) throws EucalyptusCloudException {
        // Nothing to do here
    }

    @Override
    public void checkPreconditions() throws EucalyptusCloudException {
        // If librbd is not installed, things don't get this far. The classloader tries to load Rbd JNA bindings which statically invoke librbd and things
        // go spiraling downward from there
        try {
            int[] version = Rbd.getVersion();
            if (version != null && version.length == 3) {
                LOG.info("librbd version: " + new StringBuffer().append(version[0]).append('.').append(version[1])
                        .append('.').append(version[2]).toString());
            } else {
                throw new EucalyptusCloudException("Invalid librbd version info");
            }
        } catch (Exception e) {
            LOG.warn("librbd version not found, librbd may not be installed!");
            throw new EucalyptusCloudException("librbd version not found, librbd may not be installed!", e);
        }
    }

    @Override
    public String exportResource(String volumeId, String nodeIqn, String volumeIqn)
            throws EucalyptusCloudException {
        LOG.debug("Exporting volumeId=" + volumeId + ", nodeIqn=" + nodeIqn + ", volumeIqn=" + volumeIqn
                + ". This is a no-op");
        // Volume IQN is usually in the form pool/image. This is no-op
        LOG.trace("Returning inbound argument volumeIqn=" + volumeIqn);
        return volumeIqn;
    }

    @Override
    public void unexportResource(String volumeId, String nodeIqn) throws EucalyptusCloudException {
        LOG.debug("Unexporting volumeId=" + volumeId + ", nodeIqn=" + nodeIqn + ". This is a no-op");
    }

    @Override
    public void unexportResourceFromAll(String volumeId) throws EucalyptusCloudException {
        LOG.debug("Unexporting from all volumeId=" + volumeId + ". This is a no-op");
    }

    @Override
    public void getStorageProps(ArrayList<ComponentProperty> componentProperties) {
    }

    @Override
    public void setStorageProps(ArrayList<ComponentProperty> storageProps) {
    }

    @Override
    public void stop() throws EucalyptusCloudException {
    }

    @Override
    public String getAuthType() {
        return null;
    }

    @Override
    public String getOptionalChapUser() {
        return null;
    }

    @Override
    public String createSnapshotHolder(String snapshotId, long snapSizeInMB) throws EucalyptusCloudException {
        LOG.debug("Creating snapshot holder snapshotId=" + snapshotId + ", size=" + snapSizeInMB + "MB");
        long sizeInBytes = snapSizeInMB * StorageProperties.MB; // need to go from mb to bytes
        String iqn = rbdService.createImage(snapshotId, sizeInBytes);
        return iqn;
    }

    @Override
    public boolean snapshotExists(String snapshotId, String snapshotIqn) throws EucalyptusCloudException {
        LOG.debug("Checking if snapshot exists snapshotId=" + snapshotId + ", snapshotIqn=" + snapshotIqn);
        return volumeExists(snapshotId, snapshotIqn);
    }

    @Override
    public String createSnapshotPoint(String parentVolumeId, String snapshotId, String parentVolumeIqn)
            throws EucalyptusCloudException {
        LOG.info("Creating snapshot point parentVolumeId=" + parentVolumeId + ", snapshotId=" + snapshotId
                + ", parentVolumeIqn=" + parentVolumeIqn);

        // parentVolumeIqn is of the form pool/image, get the pool information
        CanonicalRbdObject parent = CanonicalRbdObject.parse(parentVolumeIqn);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool())) {
            String snapshotPoint = CephRbdInfo.SNAPSHOT_FOR_PREFIX + snapshotId;
            String snapshotPointId = null;
            // Don't allow >1 concurrent snapshot operation on the same volume, 
            // because Ceph sometimes has failures, see EUCA-13114
            EucaSemaphore semaphore = EucaSemaphoreDirectory
                    .getSolitarySemaphore(SEMAPHORE_PREFIX + parentVolumeId);
            try {
                semaphore.acquire();
                LOG.trace("Acquired semaphore for Ceph createSnapshotPoint for volume " + parentVolumeId);
            } catch (InterruptedException ex) {
                throw new EucalyptusCloudException("Failed to create snapshot point " + snapshotId + " on volume "
                        + parentVolumeId + " as the semaphore could not be acquired");
            }
            try {
                snapshotPointId = rbdService.createSnapshot(parentVolumeId, snapshotPoint, parent.getPool());
            } finally {
                LOG.trace("Releasing semaphore for Ceph createSnapshotPoint for volume " + parentVolumeId);
                semaphore.release();
            }
            LOG.info("Created snapshot point parentVolumeId=" + parentVolumeId + ", snapshotId=" + snapshotId
                    + ", parentVolumeIqn=" + parentVolumeIqn);
            return snapshotPointId;
        } else {
            LOG.warn("Expected parentVolumeIqn format: pool/image, actual parentVolumeIqn: " + parentVolumeIqn);
            throw new EucalyptusCloudException("Failed to create snapshot point for " + snapshotId
                    + ". Expected parentVolumeIqn format: pool/image, actual parentVolumeIqn: " + parentVolumeIqn);
        }
    }

    @Override
    public void deleteSnapshotPoint(String parentVolumeId, String snapshotPointId, String parentVolumeIqn)
            throws EucalyptusCloudException {
        LOG.info("Deleting snapshot point parentVolumeId=" + parentVolumeId + ", snapshotPointId=" + snapshotPointId
                + ", parentVolumeIqn=" + parentVolumeIqn);
        // snapshotPointId is of the form pool/image@snapshot, get the pool information
        CanonicalRbdObject parent = CanonicalRbdObject.parse(snapshotPointId);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool()) && !Strings.isNullOrEmpty(parent.getImage())
                && !Strings.isNullOrEmpty(parent.getSnapshot())) {
            rbdService.deleteSnapshot(parent.getImage(), parent.getSnapshot(), parent.getPool());
        } else {
            LOG.warn("Expected snapshotPointId format: pool/image@snapshot, actual snapshotPointId: "
                    + parentVolumeIqn);
            throw new EucalyptusCloudException("Failed to delete snapshot point " + snapshotPointId
                    + ". Expected snapshotPointId format: pool/image@snapshot, actual snapshotPointId: "
                    + parentVolumeIqn);
        }
    }

    @Override
    public void checkConnectionInfo() {
        // nothing to do here
    }

    @Override
    public boolean volumeExists(String volumeId, String volumeIqn) throws EucalyptusCloudException {
        LOG.debug("Checking if volume exists volumeId=" + volumeId + ", volumeIqn=" + volumeIqn);
        try {
            // volumeIqn is of the form pool/image, get the pool information
            CanonicalRbdObject vol = CanonicalRbdObject.parse(volumeIqn);
            if (vol != null && !Strings.isNullOrEmpty(vol.getPool())) {
                return rbdService.listPool(vol.getPool()).contains(volumeId);
            } else {
                if (null != rbdService.getImagePool(volumeId)) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch (Exception e) {
            LOG.debug("Failed to find " + volumeId + ", considering volume non-existent or inaccessible to this az",
                    e);
            return false;
        }
    }

    @Override
    public String getProtocol() {
        return "rbd";
    }

    @Override
    public String getProviderName() {
        return "ceph";
    }

    class CephRbdImageDeleter extends CheckerTask {

        public CephRbdImageDeleter() {
            this.name = CephRbdImageDeleter.class.getSimpleName();
            this.runInterval = 60;
            this.runIntervalUnit = TimeUnit.SECONDS;
            this.isFixedDelay = Boolean.TRUE;
        }

        @Override
        public void run() {
            try {
                LOG.trace("Starting Ceph RBD image cleanup process");
                for (final String pool : accessiblePools) { // Cycle through all pools
                    try {
                        CephRbdImageToBeDeleted search = new CephRbdImageToBeDeleted().withPoolName(pool);

                        // Get the images that were marked for deletion from the database
                        final List<String> imagesToBeCleaned = Transactions.transform(search, IMAGE_NAME_FUNCTION);
                        LOG.trace("List of images to be cleaned up for pool " + pool + ": " + imagesToBeCleaned);

                        // Invoke clean up
                        List<String> imageSnapshotsDeleted = rbdService.cleanUpImages(pool,
                                cachedConfig.getDeletedImagePrefix(), imagesToBeCleaned);
                        if (imageSnapshotsDeleted != null && !imageSnapshotsDeleted.isEmpty()) {
                            LOG.debug("List of snapshots (on images) that were cleaned up for pool " + pool + ": "
                                    + imageSnapshotsDeleted);
                        }

                        // Delete database records of to-be-deleted images after call to rbd succeeds
                        if (imagesToBeCleaned != null && !imagesToBeCleaned.isEmpty()) {
                            Transactions.deleteAll(search, new Predicate<CephRbdImageToBeDeleted>() {
                                @Override
                                public boolean apply(CephRbdImageToBeDeleted arg0) {
                                    return imagesToBeCleaned.contains(arg0.getImageName());
                                }
                            });
                        }

                        // Delete database records of to-be-deleted snapshots for those snapshots
                        // that were actually deleted.
                        if (imageSnapshotsDeleted != null && !imageSnapshotsDeleted.isEmpty()) {
                            CephRbdSnapshotToBeDeleted searchDeleted = new CephRbdSnapshotToBeDeleted()
                                    .withPool(pool);
                            Transactions.deleteAll(searchDeleted, new Predicate<CephRbdSnapshotToBeDeleted>() {
                                @Override
                                public boolean apply(CephRbdSnapshotToBeDeleted arg0) {
                                    return imageSnapshotsDeleted.contains(arg0.getSnapshot());
                                }
                            });
                        }
                    } catch (Throwable t) {
                        LOG.debug("Encountered error while cleaning up images in pool " + pool, t);
                    }
                }
            } catch (Exception e) {
                LOG.debug("Ignoring exception during clean up of images marked for deletion", e);
            }

        }
    }

    class CephRbdSnapshotDeleter extends CheckerTask {

        public CephRbdSnapshotDeleter() {
            this.name = CephRbdSnapshotDeleter.class.getSimpleName();
            this.runInterval = 120;
            this.runIntervalUnit = TimeUnit.SECONDS;
            this.isFixedDelay = Boolean.TRUE;
        }

        @Override
        public void run() {
            try {
                LOG.trace("Starting Ceph RBD snapshot cleanup process");
                for (final String pool : accessiblePools) { // Cycle through all pools
                    try {
                        // Get the snapshots that were marked for deletion from the database, 
                        // in reverse time order so we never try to delete a parent before a child
                        List<CephRbdSnapshotToBeDeleted> listToBeDeleted = null;
                        try (TransactionResource tr = Entities.transactionFor(CephRbdSnapshotToBeDeleted.class)) {
                            listToBeDeleted = Entities
                                    .criteriaQuery(Entities.restriction(CephRbdSnapshotToBeDeleted.class)
                                            .like(CephRbdSnapshotToBeDeleted_.pool, pool).build())
                                    .orderByDesc(CephRbdSnapshotToBeDeleted_.creationTimestamp).list();
                            tr.commit();
                        } catch (Exception e) {
                            LOG.warn("Failed database lookup of snapshots marked for deletion from OSG", e);
                            return;
                        }

                        if (listToBeDeleted != null && !listToBeDeleted.isEmpty()) {
                            SetMultimap<String, String> toBeDeleted = Multimaps.newSetMultimap(Maps.newHashMap(),
                                    new Supplier<Set<String>>() {

                                        @Override
                                        public Set<String> get() {
                                            return Sets.newHashSet();
                                        }
                                    });

                            // Organize stuff into a multimap
                            for (CephRbdSnapshotToBeDeleted r : listToBeDeleted) {
                                toBeDeleted.put(r.getImage(), r.getSnapshot());
                            }
                            LOG.trace("List of snapshots to be cleaned up for pool " + pool + ": "
                                    + toBeDeleted.values());

                            // Invoke clean up
                            SetMultimap<String, String> cantBeDeleted = rbdService.cleanUpSnapshots(pool,
                                    cachedConfig.getDeletedImagePrefix(), toBeDeleted);
                            LOG.trace("List of snapshots that can't be cleaned up for pool " + pool + ": "
                                    + cantBeDeleted.values());

                            // Delete database records for all except those that couldn't be cleaned up
                            CephRbdSnapshotToBeDeleted search = new CephRbdSnapshotToBeDeleted().withPool(pool);
                            Transactions.deleteAll(search, new Predicate<CephRbdSnapshotToBeDeleted>() {
                                @Override
                                public boolean apply(CephRbdSnapshotToBeDeleted arg0) {
                                    return toBeDeleted.containsEntry(arg0.getImage(), arg0.getSnapshot())
                                            && !cantBeDeleted.containsEntry(arg0.getImage(), arg0.getSnapshot());
                                }
                            });

                        } else {
                            // nothing to do here, no snaps to be deleted in this pool
                        }
                    } catch (Throwable t) {
                        LOG.debug("Encountered error while cleaning up rbd snapshots in pool " + pool, t);
                    }
                }
            } catch (Exception e) {
                LOG.debug("Ignoring exception during clean up of rbd snapshots marked for deletion", e);
            }
        }
    }

    @Override
    public void waitAndComplete(String snapshotId, String snapshotIqn) throws EucalyptusCloudException {
        LOG.debug("Waiting for snapshot completion snapshotId=" + snapshotId + ", snapshotIqn=" + snapshotIqn);

        // snapshotIqn is of the form pool/image, get the pool information
        CanonicalRbdObject parent = CanonicalRbdObject.parse(snapshotIqn);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool())) {
            // Create a snapshot on the image for future use as one might not exist
            String snapshotPoint = CephRbdInfo.SNAPSHOT_ON_PREFIX + snapshotId;
            rbdService.createSnapshot(snapshotId, snapshotPoint, parent.getPool());
        } else {
            LOG.warn("Expected snapshotIqn format: pool/image, actual snapshotIqn: " + snapshotIqn);
            throw new EucalyptusCloudException("Failed to complete " + snapshotId
                    + ". Expected snapshotIqn format: pool/image, actual snapshotIqn: " + snapshotIqn);
        }
    }

    @Override
    public List<CheckerTask> getCheckers() {
        List<CheckerTask> list = Lists.newArrayList();
        list.add(new CephRbdImageDeleter());
        list.add(new CephRbdSnapshotDeleter());
        return list;
    }

    @Override
    public boolean supportsIncrementalSnapshots() throws EucalyptusCloudException {
        // TODO check configuration to see if delta support is enabled
        return true;
    }

    @Override
    public StorageResource generateSnapshotDelta(String volumeId, String snapshotId, String snapPointId,
            String prevSnapshotId, String prevSnapPointId) throws EucalyptusCloudException {
        LOG.info("Generating snapshot delta volumeId=" + volumeId + ", snapshot=" + snapshotId
                + ", snapshotPointId=" + snapPointId + ", prevSnapshotId=" + prevSnapshotId + ", prevSnapPointId="
                + prevSnapPointId);
        String diffName = null;
        try {
            String prevSnapPoint = null;
            if (StringUtils.isBlank(prevSnapPointId)) {
                prevSnapPoint = CephRbdInfo.SNAPSHOT_FOR_PREFIX + prevSnapshotId;
            } else if (prevSnapPointId.contains(CephRbdInfo.POOL_IMAGE_DELIMITER)
                    && prevSnapPointId.contains(CephRbdInfo.IMAGE_SNAPSHOT_DELIMITER)) {
                CanonicalRbdObject prevSnap = CanonicalRbdObject.parse(prevSnapPointId);
                if (prevSnap != null && !Strings.isNullOrEmpty(prevSnap.getSnapshot())) {
                    prevSnapPoint = prevSnap.getSnapshot();
                } else {
                    throw new EucalyptusCloudException(
                            "Invalid snapshotPointId, expected pool/image@snapshot format but got "
                                    + prevSnapPointId);
                }
            } else {
                prevSnapPoint = prevSnapPointId;
            }
            Path diffPath = Files.createTempFile(Paths.get("/var/tmp"), snapshotId + "_" + prevSnapshotId + "_",
                    ".diff");
            Files.deleteIfExists(diffPath); // Delete the file before invoking rbd. rbd does not like the file being present
            diffName = diffPath.toString();

            String[] cmd = new String[] { StorageProperties.EUCA_ROOT_WRAPPER, "rbd", "--id",
                    cachedConfig.getCephUser(), "--keyring", cachedConfig.getCephKeyringFile(), "export-diff",
                    snapPointId, diffName, "--from-snap", prevSnapPoint };
            LOG.debug("Executing: " + Joiner.on(" ").skipNulls().join(cmd));
            CommandOutput output = SystemUtil.runWithRawOutput(cmd);
            if (output != null) {
                LOG.debug("Dump from rbd command:\nreturn=" + output.returnValue + "\nstdout=" + output.output
                        + "\nstderr=" + output.error);
                if (output.returnValue != 0) {
                    throw new EucalyptusCloudException("Unable to execute rbd command. return=" + output.returnValue
                            + ", stdout=" + output.output + ", stderr=" + output.error);
                }
            }

            return new FileResource(snapshotId, diffName);
        } catch (Exception e) {
            LOG.warn("Failed to generate snapshot delta between " + snapshotId + " and " + prevSnapshotId, e);
            try {
                if (!Strings.isNullOrEmpty(diffName)) {
                    LOG.debug("Deleting file " + diffName);
                    new File(diffName).delete();
                }
            } catch (Exception ie) {
                LOG.warn("Failed to delete file " + diffName, ie);
            }
            throw new EucalyptusCloudException(
                    "Failed to generate snapshot delta between " + snapshotId + " and " + prevSnapshotId, e);
        }
    }

    @Override
    public void cleanupSnapshotDelta(String snapshotId, StorageResource sr) throws EucalyptusCloudException {
        LOG.info("Cleaning up snapshot delta for snapshotId=" + snapshotId);
        if (sr != null && !Strings.isNullOrEmpty(sr.getPath())) {
            try {
                // root wrap shell out to delete the file since its owned by root and sticky bits on /var/tmp don't let unprivileged users to delete
                String[] cmd = new String[] { StorageProperties.EUCA_ROOT_WRAPPER, "rm", "-f", sr.getPath() };
                LOG.debug("Executing: " + Joiner.on(" ").skipNulls().join(cmd));
                CommandOutput output = SystemUtil.runWithRawOutput(cmd);
                if (output != null) {
                    LOG.trace("Dump from command execution:\nreturn=" + output.returnValue + "\nstdout="
                            + output.output + "\nstderr=" + output.error);
                } else {
                    LOG.warn("Received invalid response from deletion of " + sr.getPath());
                }
            } catch (Exception e) {
                LOG.warn("Failed to delete file " + sr.getPath(), e);
            }
        }
    }

    @Override
    public String completeSnapshotBaseRestoration(String snapshotId, String baseSnapPointId, String snapshotIqn)
            throws EucalyptusCloudException {
        LOG.info("Completing restoration of base snapshotId=" + snapshotId + ", snapshotPointId=" + baseSnapPointId
                + ", snapshotIqn=" + snapshotIqn);
        // parentVolumeIqn is of the form pool/image, get the pool information
        CanonicalRbdObject parent = CanonicalRbdObject.parse(snapshotIqn);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool())) {

            // baseSnapPointId is of the form pool/image@snapshot
            CanonicalRbdObject base = CanonicalRbdObject.parse(baseSnapPointId);
            if (base != null && !Strings.isNullOrEmpty(base.getSnapshot())) {
                return rbdService.createSnapshot(snapshotId, base.getSnapshot(), parent.getPool());
            } else {
                LOG.warn("Expected baseSnapPointId format: pool/image@snapshot, actual baseSnapPointId: "
                        + baseSnapPointId);
                throw new EucalyptusCloudException("Failed to complete restoration of base for  " + snapshotId
                        + ". Expected baseSnapPointId format: pool/image@snapshot, actual baseSnapPointId: "
                        + baseSnapPointId);
            }
        } else {
            LOG.warn("Expected snapshotIqn format: pool/image, actual baseSnapPointId: " + snapshotIqn);
            throw new EucalyptusCloudException("Failed to complete restoration of base for  " + snapshotId
                    + ". Expected snapshotIqn format: pool/image, actual baseSnapPointId: " + snapshotIqn);
        }
    }

    @Override
    public void restoreSnapshotDelta(String baseIqn, StorageResource sr) throws EucalyptusCloudException {
        LOG.info("Restoring delta on base=" + baseIqn + ", snapshotId=" + sr.getId() + ", snapsDeltaFile="
                + sr.getPath());
        try {
            // Apply diff
            String[] cmd = new String[] { StorageProperties.EUCA_ROOT_WRAPPER, "rbd", "--id",
                    cachedConfig.getCephUser(), "--keyring", cachedConfig.getCephKeyringFile(), "import-diff",
                    sr.getPath(), baseIqn };
            LOG.debug("Executing: " + Joiner.on(" ").skipNulls().join(cmd));
            CommandOutput output = SystemUtil.runWithRawOutput(cmd);
            if (output != null) {
                LOG.debug("Dump from rbd command:\nReturn value=" + output.returnValue + "\nOutput=" + output.output
                        + "\nDebug=" + output.error);
                if (output.returnValue != 0) {
                    throw new EucalyptusCloudException("Unable to execute rbd command. Failed with error: "
                            + output.output + "\n" + output.error);
                }
            }
        } catch (Exception e) {
            LOG.warn("Failed to apply snapshot delta on " + baseIqn, e);
            throw new EucalyptusCloudException("Failed to apply snapshot delta on " + baseIqn, e);
        } finally {
            // clean up the diff file
            try {
                LOG.trace("About to delete diff file " + sr.getPath());
                if (!Files.deleteIfExists(Paths.get(sr.getPath()))) {
                    LOG.warn("Diff file " + sr.getPath() + "did not exist to delete.");
                } else {
                    LOG.trace("Successfully deleted diff file " + sr.getPath());
                }
            } catch (Exception e) {
                LOG.warn("Failed to delete diff file " + sr.getPath(), e);
            }
        }
    }

    @Override
    public void completeSnapshotDeltaRestoration(String snapshotId, String snapshotIqn)
            throws EucalyptusCloudException {
        LOG.info("Cleaning up all existing rbd snapshots and creating a fresh new one, snapshotId=" + snapshotId
                + ", snapshotIqn=" + snapshotIqn);

        // snapshotIqn is of the form pool/image, get the pool information
        CanonicalRbdObject parent = CanonicalRbdObject.parse(snapshotIqn);
        if (parent != null && !Strings.isNullOrEmpty(parent.getPool())
                && !Strings.isNullOrEmpty(parent.getImage())) {
            rbdService.deleteAllSnapshots(parent.getImage(), parent.getPool(),
                    CephRbdInfo.SNAPSHOT_ON_PREFIX + snapshotId);
        } else {
            LOG.warn("Expected snapshotIqn format: pool/image, actual snapshotIqn: " + snapshotIqn);
            throw new EucalyptusCloudException("Failed to clean up and restore " + snapshotId
                    + ". Expected snapshotIqn format: pool/image, actual snapshotIqn: " + snapshotIqn);
        }
    }
}