com.eucalyptus.blockstorage.SnapshotUpdateEventListener.java Source code

Java tutorial

Introduction

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

Source

/*************************************************************************
 * Copyright 2009-2015 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 java.util.Collections.unmodifiableSet;
import static java.util.EnumSet.of;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import org.apache.log4j.Logger;
import com.eucalyptus.blockstorage.msgs.DescribeStorageSnapshotsResponseType;
import com.eucalyptus.blockstorage.msgs.DescribeStorageSnapshotsType;
import com.eucalyptus.blockstorage.msgs.StorageSnapshot;
import com.eucalyptus.bootstrap.Bootstrap;
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.common.internal.blockstorage.Snapshot;
import com.eucalyptus.compute.common.internal.blockstorage.Snapshots;
import com.eucalyptus.compute.common.internal.blockstorage.State;
import com.eucalyptus.compute.common.internal.blockstorage.Volume;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.event.ClockTick;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.event.ListenerRegistry;
import com.eucalyptus.event.Listeners;
import com.eucalyptus.records.Logs;
import com.eucalyptus.reporting.event.SnapShotEvent;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.util.async.CheckedListenableFuture;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;

/**
 *
 */
public class SnapshotUpdateEventListener implements EventListener<ClockTick>, Callable<Boolean> {
    private static final Logger LOG = Logger.getLogger(SnapshotUpdateEventListener.class);
    private static final long SNAPSHOT_STATE_TIMEOUT = 2 * 60 * 60 * 1000L;
    private static final Set<State> SNAPSHOT_TIMEOUT_STATES = unmodifiableSet(of(State.NIHIL, State.GENERATING));
    private static final AtomicBoolean ready = new AtomicBoolean(true);

    public static void register() {
        Listeners.register(ClockTick.class, new SnapshotUpdateEventListener());
    }

    @Override
    public void fireEvent(ClockTick event) {
        if (Topology.isEnabledLocally(Eucalyptus.class) && Bootstrap.isOperational()
                && ready.compareAndSet(true, false)) {
            try {
                Threads.enqueue(Eucalyptus.class, Snapshots.class, this);
            } catch (Exception ex) {
                ready.set(true);
            }
        }
    }

    @Override
    public Boolean call() throws Exception {
        try {
            try {
                final Multimap<String, Snapshot> snapshots = ArrayListMultimap.create();
                for (Snapshot s : Snapshots.list()) {
                    snapshots.put(s.getPartition(), s);
                }
                final Map<String, Collection<Snapshot>> snapshotsByPartition = ImmutableMap
                        .copyOf(snapshots.asMap());
                final Map<String, Supplier<Map<String, StorageSnapshot>>> scSnapshotsByPartition = Maps
                        .newHashMap();
                for (final String partition : snapshotsByPartition.keySet()) {
                    scSnapshotsByPartition.put(partition, getSnapshotsInPartition(partition));
                }
                for (final String partition : snapshotsByPartition.keySet()) {
                    try {
                        final Map<String, StorageSnapshot> storageSnapshots = scSnapshotsByPartition.get(partition)
                                .get();
                        for (final Snapshot snapshot : snapshotsByPartition.get(partition)) {
                            final StorageSnapshot storageSnapshot = storageSnapshots
                                    .remove(snapshot.getDisplayName());
                            updateSnapshot(snapshot, storageSnapshot);
                        }
                        for (StorageSnapshot unknownSnapshot : storageSnapshots.values()) {
                            LOG.trace("SnapshotStateUpdate: found unknown snapshot: "
                                    + unknownSnapshot.getSnapshotId() + " " + unknownSnapshot.getStatus());
                        }
                    } catch (Exception ex) {
                        LOG.error(ex);
                        Logs.extreme().error(ex, ex);
                    }
                }
            } catch (Exception ex) {
                LOG.error(ex);
                Logs.extreme().error(ex, ex);
            }
        } finally {
            ready.set(true);
        }
        return true;
    }

    private static Supplier<Map<String, StorageSnapshot>> getSnapshotsInPartition(final String partition) {
        final ServiceConfiguration scConfig = Topology.lookup(Storage.class, Partitions.lookupByName(partition));
        try {
            final CheckedListenableFuture<DescribeStorageSnapshotsResponseType> describeFuture = AsyncRequests
                    .dispatch(scConfig, new DescribeStorageSnapshotsType());
            return new Supplier<Map<String, StorageSnapshot>>() {
                @Override
                public Map<String, StorageSnapshot> get() {
                    final Map<String, StorageSnapshot> storageSnapshots = Maps.newHashMap();
                    try {
                        final DescribeStorageSnapshotsResponseType snapshotInfo = describeFuture.get();
                        for (final StorageSnapshot storageSnapshot : snapshotInfo.getSnapshotSet()) {
                            storageSnapshots.put(storageSnapshot.getSnapshotId(), storageSnapshot);
                        }
                    } catch (final Exception ex) {
                        LOG.error(ex);
                        Logs.extreme().error(ex, ex);
                    }
                    return storageSnapshots;
                }
            };
        } catch (final Exception ex) {
            LOG.error(ex);
            Logs.extreme().error(ex, ex);
        }
        return Suppliers.ofInstance(Collections.<String, StorageSnapshot>emptyMap());
    }

    private static final class SnapshotStateChange {
        private final boolean timedOut;
        @Nonnull
        private final State newState; // could be the same as the old state
        @Nonnull
        private final Optional<String> progress;

        SnapshotStateChange(final boolean timedOut, @Nonnull final State newState,
                @Nonnull final Optional<String> progress) {
            this.timedOut = timedOut;
            this.newState = newState;
            this.progress = progress;
        }

        boolean willUpdate(final Snapshot entity) {
            return timedOut || newState != entity.getState()
                    || progress.isPresent() && !progress.get().equals(entity.getProgress());
        }

        /**
         * Apply these changes to the given entity
         */
        void update(final Snapshot entity) {
            if (timedOut) {
                Entities.delete(entity);
            } else {
                if (progress.isPresent()) {
                    entity.setProgress(progress.get());
                }

                entity.setState(newState);
            }
        }
    }

    /**
     * Determine changes to apply to entity, do not update entity here.
     */
    private static SnapshotStateChange changesFor(final Snapshot entity, final StorageSnapshot storageSnapshot) {
        boolean timedOut = false;
        State newState = entity.getState();
        Optional<String> progressUpdate = Optional.absent();
        if (storageSnapshot != null) {
            if (storageSnapshot.getStatus() != null) {
                final Optional<State> stateUpdate = StorageUtil.mapState(storageSnapshot.getStatus());
                if (stateUpdate != null) {
                    newState = stateUpdate.get();
                }
            }

            if (!State.EXTANT.equals(newState) && storageSnapshot.getProgress() != null) {
                progressUpdate = Optional.of(storageSnapshot.getProgress());
            } else if (State.EXTANT.equals(newState)) {
                progressUpdate = Optional.of("100%");
            } else if (State.GENERATING.equals(newState)) {
                if (entity.getProgress() == null) {
                    progressUpdate = Optional.of("0%");
                }
            }
        } else if (SNAPSHOT_TIMEOUT_STATES.contains(entity.getState())
                && entity.lastUpdateMillis() > SNAPSHOT_STATE_TIMEOUT) {
            timedOut = true;
        } else {
            if (State.EXTANT.equals(entity.getState())) {
                progressUpdate = Optional.of("100%");
            } else if (State.GENERATING.equals(entity.getState())) {
                if (entity.getProgress() == null) {
                    progressUpdate = Optional.of("0%");
                }
            }
        }
        return new SnapshotStateChange(timedOut, newState, progressUpdate);
    }

    private static void updateSnapshot(final Snapshot snapshot, final StorageSnapshot storageSnapshot) {
        try {
            final Function<String, Optional<SnapShotEvent>> updateSnapshot = new Function<String, Optional<SnapShotEvent>>() {
                public Optional<SnapShotEvent> apply(final String input) {
                    Optional<SnapShotEvent> event = Optional.absent();
                    try {
                        final Snapshot entity = Entities.uniqueResult(Snapshot.named(null, input));
                        final SnapshotStateChange stateChange = changesFor(entity, storageSnapshot);

                        final StringBuilder buf = new StringBuilder();
                        buf.append("SnapshotStateUpdate: ").append(entity.getPartition()).append(" ").append(input)
                                .append(" ").append(entity.getParentVolume()).append(" ").append(entity.getState())
                                .append(" ").append(entity.getProgress()).append(" ");

                        if (entity.getState() == State.GENERATING && stateChange.newState == State.EXTANT) {
                            //Went from GENERATING->EXTANT. Do the reporting event fire here.
                            try {
                                final Volume volume = Entities
                                        .uniqueResult(Volume.named(null, storageSnapshot.getVolumeId()));
                                final String volumeUuid = volume.getNaturalId();
                                event = Optional.of(SnapShotEvent.with(
                                        SnapShotEvent.forSnapShotCreate(entity.getVolumeSize(), volumeUuid,
                                                entity.getParentVolume()),
                                        entity.getNaturalId(), entity.getDisplayName(), entity.getOwnerUserId(),
                                        entity.getOwnerUserName(), entity.getOwnerAccountNumber()));
                            } catch (final Throwable e) {
                                LOG.error(
                                        "Error inserting/creating reporting event for snapshot creation of snapshot: "
                                                + entity.getDisplayName(),
                                        e);
                            }
                        }

                        // all changes to entity state must be via update
                        stateChange.update(entity);

                        if (storageSnapshot != null) {
                            buf.append(" storage-snapshot ").append(storageSnapshot.getStatus()).append("=>")
                                    .append(entity.getState()).append(" ").append(storageSnapshot.getProgress())
                                    .append(" ");
                        }

                        LOG.debug(buf.toString());
                        return event;
                    } catch (TransactionException ex) {
                        throw Exceptions.toUndeclared(ex);
                    }
                }
            };
            if (changesFor(snapshot, storageSnapshot).willUpdate(snapshot)) {
                try {
                    final Optional<SnapShotEvent> event = Entities.asTransaction(Snapshot.class, updateSnapshot)
                            .apply(snapshot.getDisplayName());
                    //noinspection ConstantConditions
                    if (event.isPresent()) {
                        ListenerRegistry.getInstance().fireEvent(event.get());
                    }
                } catch (Exception ex) {
                    LOG.error(ex);
                    Logs.extreme().error(ex, ex);
                }
            }
        } catch (Exception ex) {
            LOG.error(ex);
            Logs.extreme().error(ex, ex);
        }
    }
}