com.eucalyptus.blockstorage.SnapshotManager.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.blockstorage.SnapshotManager.java

Source

/*************************************************************************
 * Copyright 2009-2013 Eucalyptus Systems, Inc.
 *
 * 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/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 *
 * 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;

import static com.eucalyptus.compute.common.ImageMetadata.State.available;
import static com.eucalyptus.compute.common.ImageMetadata.State.pending;
import static com.eucalyptus.images.Images.inState;

import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import javax.annotation.Nullable;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;

import org.apache.log4j.Logger;

import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.blockstorage.msgs.DeleteStorageSnapshotResponseType;
import com.eucalyptus.blockstorage.msgs.DeleteStorageSnapshotType;
import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.cloud.util.DuplicateMetadataException;
import com.eucalyptus.component.NoSuchComponentException;
import com.eucalyptus.component.Partitions;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.compute.ClientComputeException;
import com.eucalyptus.compute.identifier.InvalidResourceIdentifier;
import com.eucalyptus.compute.identifier.ResourceIdentifiers;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.context.IllegalContextAccessException;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.event.ListenerRegistry;
import com.eucalyptus.images.Images;
import com.eucalyptus.records.Logs;
import com.eucalyptus.reporting.event.EventActionInfo;
import com.eucalyptus.reporting.event.SnapShotEvent;
import com.eucalyptus.reporting.event.SnapShotEvent.SnapShotAction;
import com.eucalyptus.system.Threads;
import com.eucalyptus.tags.Filter;
import com.eucalyptus.tags.Filters;
import com.eucalyptus.tags.Tag;
import com.eucalyptus.tags.TagSupport;
import com.eucalyptus.tags.Tags;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.RestrictedTypes;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.vm.VmInstance;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import edu.ucsb.eucalyptus.msgs.CreateSnapshotResponseType;
import edu.ucsb.eucalyptus.msgs.CreateSnapshotType;
import edu.ucsb.eucalyptus.msgs.DeleteSnapshotResponseType;
import edu.ucsb.eucalyptus.msgs.DeleteSnapshotType;
import edu.ucsb.eucalyptus.msgs.DescribeSnapshotAttributeResponseType;
import edu.ucsb.eucalyptus.msgs.DescribeSnapshotAttributeType;
import edu.ucsb.eucalyptus.msgs.DescribeSnapshotsResponseType;
import edu.ucsb.eucalyptus.msgs.DescribeSnapshotsType;
import edu.ucsb.eucalyptus.msgs.ModifySnapshotAttributeResponseType;
import edu.ucsb.eucalyptus.msgs.ModifySnapshotAttributeType;
import edu.ucsb.eucalyptus.msgs.ResetSnapshotAttributeResponseType;
import edu.ucsb.eucalyptus.msgs.ResetSnapshotAttributeType;
import edu.ucsb.eucalyptus.msgs.ResourceTag;

public class SnapshotManager {

    static Logger LOG = Logger.getLogger(SnapshotManager.class);
    static String ID_PREFIX = "snap";

    public CreateSnapshotResponseType create(final CreateSnapshotType request)
            throws EucalyptusCloudException, NoSuchComponentException, DuplicateMetadataException, AuthException,
            IllegalContextAccessException, NoSuchElementException, PersistenceException, TransactionException {
        final Context ctx = Contexts.lookup();
        final String volumeId = normalizeVolumeIdentifier(request.getVolumeId());
        final Volume vol;
        try {
            vol = Transactions.find(Volume.named(ctx.getUserFullName().asAccountFullName(), volumeId));
        } catch (NoSuchElementException e) {
            throw new ClientComputeException("InvalidVolume.NotFound",
                    "Volume not found '" + request.getVolumeId() + "'");
        }
        final ServiceConfiguration sc = Topology.lookup(Storage.class, Partitions.lookupByName(vol.getPartition()));
        final Volume volReady = Volumes.checkVolumeReady(vol);
        Supplier<Snapshot> allocator = new Supplier<Snapshot>() {

            @Override
            public Snapshot get() {
                try {
                    return Snapshots.initializeSnapshot(ctx.getUserFullName(), volReady, sc,
                            request.getDescription());
                } catch (EucalyptusCloudException ex) {
                    throw new RuntimeException(ex);
                }
            }
        };
        Snapshot snap = RestrictedTypes.allocateUnitlessResource(allocator);
        try {
            snap = Snapshots.startCreateSnapshot(volReady, snap);
        } catch (EucalyptusCloudException e) {
            final EntityTransaction db = Entities.get(Snapshot.class);
            try {
                Snapshot entity = Entities.uniqueResult(snap);
                Entities.delete(entity);
                db.commit();
            } catch (Exception ex) {
                Logs.extreme().error(ex, ex);
            } finally {
                if (db.isActive())
                    db.rollback();
            }
            throw e;
        }

        try {
            fireUsageEvent(snap, SnapShotEvent.forSnapShotCreate(snap.getVolumeSize(), volReady.getNaturalId(),
                    snap.getDisplayName()));
        } catch (Throwable reportEx) {
            LOG.error("Unable to fire snap shot creation reporting event", reportEx);
        }

        CreateSnapshotResponseType reply = (CreateSnapshotResponseType) request.getReply();
        edu.ucsb.eucalyptus.msgs.Snapshot snapMsg = snap.morph(new edu.ucsb.eucalyptus.msgs.Snapshot());
        snapMsg.setProgress("0%");
        snapMsg.setOwnerId(snap.getOwnerAccountNumber());
        snapMsg.setVolumeSize(volReady.getSize().toString());
        reply.setSnapshot(snapMsg);
        return reply;
    }

    public DeleteSnapshotResponseType delete(final DeleteSnapshotType request) throws EucalyptusCloudException {
        final DeleteSnapshotResponseType reply = (DeleteSnapshotResponseType) request.getReply();
        final Context ctx = Contexts.lookup();
        final String snapshotId = normalizeSnapshotIdentifier(request.getSnapshotId());
        Predicate<Snapshot> deleteSnapshot = new Predicate<Snapshot>() {

            @Override
            public boolean apply(Snapshot snap) {
                if (!State.EXTANT.equals(snap.getState()) && !State.FAIL.equals(snap.getState())) {
                    return false;
                } else if (!RestrictedTypes.filterPrivileged().apply(snap)) {
                    throw Exceptions.toUndeclared(new EucalyptusCloudException("Not authorized to delete snapshot "
                            + request.getSnapshotId() + " by " + ctx.getUser().getName()));
                } else if (isReservedSnapshot(snapshotId)) {
                    throw Exceptions.toUndeclared(new EucalyptusCloudException(
                            "Snapshot " + request.getSnapshotId() + " is in use, deletion not permitted"));
                } else {
                    fireUsageEvent(snap, SnapShotEvent.forSnapShotDelete());
                    final String partition = snap.getPartition();
                    final String snapshotId = snap.getDisplayName();
                    Callable<Boolean> deleteBroadcast = new Callable<Boolean>() {
                        public Boolean call() {
                            final DeleteStorageSnapshotType deleteMsg = new DeleteStorageSnapshotType(snapshotId);
                            return Iterables.all(Topology.enabledServices(Storage.class),
                                    new Predicate<ServiceConfiguration>() {

                                        @Override
                                        public boolean apply(ServiceConfiguration arg0) {
                                            if (!arg0.getPartition().equals(partition)) {
                                                try {
                                                    AsyncRequests.sendSync(arg0, deleteMsg);
                                                } catch (Exception ex) {
                                                    LOG.error(ex);
                                                    Logs.extreme().error(ex, ex);
                                                }
                                            }
                                            return true;
                                        }
                                    });
                        }
                    };

                    ServiceConfiguration sc = null;
                    try {
                        sc = Topology.lookup(Storage.class, Partitions.lookupByName(snap.getPartition()));
                    } catch (final Exception ex) {
                        sc = null;
                    }
                    if (sc != null) {
                        try {
                            DeleteStorageSnapshotResponseType scReply = AsyncRequests.sendSync(sc,
                                    new DeleteStorageSnapshotType(snap.getDisplayName()));
                            if (scReply.get_return()) {
                                Threads.enqueue(Eucalyptus.class, Snapshots.class, deleteBroadcast);
                            } else {
                                throw Exceptions.toUndeclared(
                                        new EucalyptusCloudException("Unable to delete snapshot: " + snap));
                            }
                        } catch (Exception ex1) {
                            throw Exceptions.toUndeclared(ex1.getMessage(), ex1);
                        }
                    } else {
                        Threads.enqueue(Eucalyptus.class, Snapshots.class, deleteBroadcast);
                    }
                    return true;
                }
            }
        };
        boolean result = false;
        try {
            result = Transactions.delete(Snapshot.named(ctx.getUserFullName().asAccountFullName(), snapshotId),
                    deleteSnapshot);
        } catch (NoSuchElementException ex2) {
            try {
                result = Transactions.delete(Snapshot.named(null, snapshotId), deleteSnapshot);
            } catch (ExecutionException ex3) {
                throw new EucalyptusCloudException(ex3.getCause());
            } catch (NoSuchElementException ex4) {
                throw new ClientComputeException("InvalidSnapshot.NotFound",
                        "The snapshot '" + request.getSnapshotId() + "' does not exist.");
            }
        } catch (ExecutionException ex1) {
            throw new EucalyptusCloudException(ex1.getCause());
        }
        reply.set_return(result);
        return reply;
    }

    public DescribeSnapshotsResponseType describe(final DescribeSnapshotsType request)
            throws EucalyptusCloudException {
        final DescribeSnapshotsResponseType reply = (DescribeSnapshotsResponseType) request.getReply();
        if (!request.getRestorableBySet().isEmpty()) {
            return reply;
        } //TODO:KEN EUCA-5759 Need to implement RestorableBy, for now ignore
        final Context ctx = Contexts.lookup();
        final boolean showAll = request.getSnapshotSet().remove("verbose");
        final Set<String> snapshotIds = Sets.newHashSet(normalizeSnapshotIdentifiers(request.getSnapshotSet()));
        final AccountFullName ownerFullName = (ctx.isAdministrator() && showAll) ? null
                : AccountFullName.getInstance(ctx.getAccount());
        final Filter filter = Filters.generate(request.getFilterSet(), Snapshot.class);
        final EntityTransaction db = Entities.get(Snapshot.class);
        try {
            final List<Snapshot> unfilteredSnapshots = Entities.query(Snapshot.named(ownerFullName, null), true,
                    filter.asCriterion(), filter.getAliases());
            final Predicate<? super Snapshot> requestedAndAccessible = CloudMetadatas.filteringFor(Snapshot.class)
                    .byId(snapshotIds).byPredicate(filter.asPredicate()).byPrivileges().buildPredicate();

            final Iterable<Snapshot> snapshots = Iterables.filter(unfilteredSnapshots, requestedAndAccessible);
            final Map<String, List<Tag>> tagsMap = TagSupport.forResourceClass(Snapshot.class).getResourceTagMap(
                    AccountFullName.getInstance(ctx.getAccount()),
                    Iterables.transform(snapshots, CloudMetadatas.toDisplayName()));
            for (final Snapshot snap : snapshots) {
                try {
                    final edu.ucsb.eucalyptus.msgs.Snapshot snapReply = snap
                            .morph(new edu.ucsb.eucalyptus.msgs.Snapshot());
                    Tags.addFromTags(snapReply.getTagSet(), ResourceTag.class,
                            tagsMap.get(snapReply.getSnapshotId()));
                    snapReply.setVolumeId(snap.getParentVolume());
                    snapReply.setOwnerId(snap.getOwnerAccountNumber());
                    reply.getSnapshotSet().add(snapReply);
                } catch (NoSuchElementException e) {
                    LOG.warn("Error getting snapshot information from the Storage Controller: " + e);
                    LOG.debug(e, e);
                }
            }
        } finally {
            db.rollback();
        }
        return reply;
    }

    public ResetSnapshotAttributeResponseType resetSnapshotAttribute(ResetSnapshotAttributeType request) {
        ResetSnapshotAttributeResponseType reply = request.getReply();
        return reply;
    }

    public ModifySnapshotAttributeResponseType modifySnapshotAttribute(ModifySnapshotAttributeType request) {
        ModifySnapshotAttributeResponseType reply = request.getReply();
        return reply;
    }

    public DescribeSnapshotAttributeResponseType describeSnapshotAttribute(DescribeSnapshotAttributeType request) {
        DescribeSnapshotAttributeResponseType reply = request.getReply();
        return reply;
    }

    private static boolean isReservedSnapshot(final String snapshotId) {
        // Fix for EUCA-4932. Any snapshot associated with an (available or pending) image as a root/non-root device is a reserved snapshot
        // and can't be deleted without first unregistering the image
        return Predicates.or(SnapshotInUseVerifier.INSTANCE).apply(snapshotId);
    }

    private static String normalizeIdentifier(final String identifier, final String prefix, final boolean required,
            final String message) throws ClientComputeException {
        try {
            return Strings.emptyToNull(identifier) == null && !required ? null
                    : ResourceIdentifiers.parse(prefix, identifier).getIdentifier();
        } catch (final InvalidResourceIdentifier e) {
            throw new ClientComputeException("InvalidParameterValue", String.format(message, e.getIdentifier()));
        }
    }

    private static String normalizeSnapshotIdentifier(final String identifier) throws EucalyptusCloudException {
        return normalizeIdentifier(identifier, ID_PREFIX, true,
                "Value (%s) for parameter snapshotId is invalid. Expected: 'snap-...'.");
    }

    private static String normalizeVolumeIdentifier(final String identifier) throws EucalyptusCloudException {
        return normalizeIdentifier(identifier, Volumes.ID_PREFIX, true,
                "Value (%s) for parameter volume is invalid. Expected: 'vol-...'.");
    }

    private static List<String> normalizeSnapshotIdentifiers(final List<String> identifiers)
            throws EucalyptusCloudException {
        try {
            return ResourceIdentifiers.normalize(ID_PREFIX, identifiers);
        } catch (final InvalidResourceIdentifier e) {
            throw new ClientComputeException("InvalidParameterValue",
                    "Value (" + e.getIdentifier() + ") for parameter snapshots is invalid. Expected: 'snap-...'.");
        }
    }

    private enum ImageSnapshotReservation implements Predicate<String> {
        INSTANCE;

        @Override
        public boolean apply(final String identifier) {
            return Iterables.any(Entities.query(Images.exampleBlockStorageWithSnapshotId(identifier), true),
                    inState(EnumSet.of(pending, available)));
        }
    }

    /**
     * <p>Predicate to check if a snapshot is associated with a pending or available boot from ebs image.
     * Returns true if the snapshot is used in the image registration with root or non root devices.</p>
     */
    private enum SnapshotInUseVerifier implements Predicate<String> {
        INSTANCE;

        @Override
        public boolean apply(final String identifier) {
            return Iterables.any(Entities.query(Images.exampleBSDMappingWithSnapshotId(identifier), true),
                    Images.imageInState(EnumSet.of(pending, available)));
        }
    }

    private static void fireUsageEvent(final Snapshot snap, final EventActionInfo<SnapShotAction> actionInfo) {
        try {
            ListenerRegistry.getInstance().fireEvent(SnapShotEvent.with(actionInfo, snap.getNaturalId(),
                    snap.getDisplayName(), snap.getOwner().getUserId()));
        } catch (final Throwable e) {
            LOG.error(e, e);
        }
    }
}