Java tutorial
/************************************************************************* * 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 java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import com.eucalyptus.blockstorage.msgs.DescribeStorageVolumesResponseType; import com.eucalyptus.blockstorage.msgs.DescribeStorageVolumesType; import com.eucalyptus.blockstorage.msgs.StorageVolume; 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.State; import com.eucalyptus.compute.common.internal.blockstorage.Volume; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.event.ClockTick; import com.eucalyptus.event.EventListener; import com.eucalyptus.event.Listeners; import com.eucalyptus.records.Logs; import com.eucalyptus.reporting.event.VolumeEvent; 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.base.Throwables; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; /** * */ public class VolumeUpdateEventListener implements EventListener<ClockTick>, Callable<Boolean> { private static final Logger LOG = Logger.getLogger(VolumeUpdateEventListener.class); private static final long VOLUME_STATE_TIMEOUT = 2 * 60 * 60 * 1000L; private static final long VOLUME_DELETE_TIMEOUT = 30 * 60 * 1000L; private static final AtomicBoolean ready = new AtomicBoolean(true); private static final ConcurrentMap<String, Long> pendingUpdates = Maps.newConcurrentMap(); public static void register() { Listeners.register(ClockTick.class, new VolumeUpdateEventListener()); } @Override public void fireEvent(final ClockTick event) { if (Topology.isEnabledLocally(Eucalyptus.class) && Bootstrap.isOperational() && ready.compareAndSet(true, false)) { try { Threads.enqueue(Eucalyptus.class, Volumes.class, this); } catch (final Exception ex) { ready.set(true); } } } @Override public Boolean call() throws Exception { try { VolumeUpdateEventListener.update(); } finally { ready.set(true); } return true; } static void update() { final Multimap<String, Volume> partitionVolumeMap = HashMultimap.create(); try (final TransactionResource tx = Entities.readOnlyDistinctTransactionFor(Volume.class)) { for (final Volume v : Entities.query(Volume.named(null, null))) { partitionVolumeMap.put(v.getPartition().intern(), v); } } catch (final Exception ex) { Logs.extreme().error(ex, ex); } final Map<String, Collection<Volume>> volumesByPartition = ImmutableMap.copyOf(partitionVolumeMap.asMap()); final Map<String, Supplier<Map<String, StorageVolume>>> scVolumesByPartition = Maps.newHashMap(); for (final String partition : volumesByPartition.keySet()) { scVolumesByPartition.put(partition, updateVolumesInPartition(partition));//TODO:GRZE: restoring volume state } for (final String partition : volumesByPartition.keySet()) { try { final Map<String, StorageVolume> idStorageVolumeMap = scVolumesByPartition.get(partition).get(); for (final Volume v : volumesByPartition.get(partition)) { try { final StorageVolume storageVolume = idStorageVolumeMap.get(v.getDisplayName()); if (pendingUpdates.putIfAbsent(v.getDisplayName(), System.currentTimeMillis()) == null) try { Threads.enqueue(Storage.class, VolumeUpdateEventListener.class, (Runtime.getRuntime().availableProcessors() * 2) + 1, new Callable<Void>() { @Override public Void call() throws Exception { try { volumeStateUpdate(v, storageVolume); } finally { pendingUpdates.remove(v.getDisplayName()); } return null; } }); } catch (Throwable t) { pendingUpdates.remove(v.getDisplayName()); throw Throwables.propagate(t); } } catch (final Exception ex) { LOG.error(ex); Logs.extreme().error(ex, ex); } } } catch (final Exception ex) { LOG.error(ex); Logs.extreme().error(ex, ex); } } } static void volumeStateUpdate(final Volume volume, final StorageVolume storageVolume) { final StringBuilder buf = new StringBuilder(); final Function<String, Volume> updateVolume = new Function<String, Volume>() { @Override public Volume apply(final String input) { try { final Volume volumeToUpdate = Entities.uniqueResult(Volume.named(null, input)); State volumeState = volumeToUpdate.getState(); Integer size = 0; final Optional<State> newState = calculateState(volumeToUpdate, storageVolume == null ? null : storageVolume.getStatus()); if (storageVolume != null) { size = Integer.parseInt(storageVolume.getSize()); } if (newState.isPresent()) { volumeState = newState.get(); } volumeToUpdate.setState(volumeState); try { if (volumeToUpdate.getSize() <= 0) { volumeToUpdate.setSize(size); if (EnumSet.of(State.GENERATING, State.EXTANT, State.BUSY).contains(volumeState)) { Volumes.fireUsageEvent(volumeToUpdate, VolumeEvent.forVolumeCreate()); } } } catch (final Exception ex) { LOG.error(ex); Logs.extreme().error(ex, ex); } //TODO:GRZE: expire deleted/failed volumes in the future. // if ( State.ANNIHILATED.equals( v.getState( ) ) && State.ANNIHILATED.equals( v.getState( ) ) && v.lastUpdateMillis( ) > VOLUME_DELETE_TIMEOUT ) { // Entities.delete( v ); // } buf.append(" Resulting new-state: [").append(volumeToUpdate.getState()).append("]"); LOG.debug(buf.toString()); return volumeToUpdate; } catch (final TransactionException ex) { LOG.error(buf.toString() + " failed because of " + ex.getMessage()); Logs.extreme().error(buf.toString() + " failed because of " + ex.getMessage(), ex); throw Exceptions.toUndeclared(ex); } catch (final NoSuchElementException ex) { LOG.error(buf.toString() + " failed because of " + ex.getMessage()); Logs.extreme().error(buf.toString() + " failed because of " + ex.getMessage(), ex); throw ex; } } }; final Optional<State> newState = calculateState(volume, storageVolume == null ? null : storageVolume.getStatus()); buf.append("VolumeStateUpdate: Current Volume Info: [").append("Partition: ").append(volume.getPartition()) .append(" ").append("Name: ").append(volume.getDisplayName()).append(" ").append("CurrentState: ") .append(volume.getState()).append(" ").append("Created: ").append(volume.getCreationTimestamp()) .append(" ]"); if (storageVolume != null) { buf.append(" Incoming state update: [").append("State: ").append(storageVolume.getStatus()).append("=>") .append(newState).append(" ").append("Size: ").append(storageVolume.getSize()).append("GB ") .append("SourceSnapshotId: ").append(storageVolume.getSnapshotId()).append(" ") .append("CreationTime: ").append(storageVolume.getCreateTime()).append(" ") .append("DeviceName: ").append(storageVolume.getActualDeviceName()).append(" ] "); } if (volume.getSize() <= 0 || (newState.isPresent() && newState.get() != volume.getState())) { Entities.asTransaction(Volume.class, updateVolume).apply(volume.getDisplayName()); } else { LOG.debug(buf.toString() + " unchanged"); } } static Optional<State> calculateState(final Volume volumeToUpdate, final String storageStatus) { Optional<State> state = Optional.of(volumeToUpdate.getState()); if (storageStatus != null) { String status = storageStatus; //NOTE: removed this conditional check for initial state and actual device name. An empty actualDeviceName or 'invalid' //is legitimate if the volume is not exported/attached. Only on attachment request will device name be populated //if ( State.EXTANT.equals( initialState ) // && ( ( actualDeviceName == null ) || "invalid".equals( actualDeviceName ) || "unknown".equals( actualDeviceName ) ) ) { // // volumeState = State.GENERATING; //} else if ( State.ANNIHILATING.equals( initialState ) && State.ANNIHILATED.equals( Volumes.transformStorageState( v.getState( ), status ) ) ) { if (State.ANNIHILATING == volumeToUpdate.getState() && State.ANNIHILATED == Volumes.transformStorageState(volumeToUpdate.getState(), status)) { state = Optional.of(State.ANNIHILATED); } else { state = Optional.of(Volumes.transformStorageState(volumeToUpdate.getState(), status)); } } else if (State.ANNIHILATING.equals(volumeToUpdate.getState())) { state = Optional.of(State.ANNIHILATED); } else if (State.GENERATING.equals(volumeToUpdate.getState()) && volumeToUpdate.lastUpdateMillis() > VOLUME_STATE_TIMEOUT) { state = Optional.of(State.FAIL); } else if (State.EXTANT.equals(volumeToUpdate.getState()) && volumeToUpdate.lastUpdateMillis() > VOLUME_STATE_TIMEOUT) { //volume is available but the SC does not know about it. //This is based on a guarantee that the SC will never send partial information //If the SC subsequently reports it as available, it will be recovered state = Optional.of(State.ERROR); } return state; } static Supplier<Map<String, StorageVolume>> updateVolumesInPartition(final String partition) { final ServiceConfiguration scConfig = Topology.lookup(Storage.class, Partitions.lookupByName(partition)); try { final CheckedListenableFuture<DescribeStorageVolumesResponseType> describeFuture = AsyncRequests .dispatch(scConfig, new DescribeStorageVolumesType()); return new Supplier<Map<String, StorageVolume>>() { @Override public Map<String, StorageVolume> get() { final Map<String, StorageVolume> idStorageVolumeMap = Maps.newHashMap(); try { final DescribeStorageVolumesResponseType volState = describeFuture.get(); for (final StorageVolume vol : volState.getVolumeSet()) { LOG.trace("Volume states: " + vol.getVolumeId() + " " + vol.getStatus() + " " + vol.getActualDeviceName()); idStorageVolumeMap.put(vol.getVolumeId(), vol); } } catch (final Exception ex) { LOG.error(ex); Logs.extreme().error(ex, ex); } return idStorageVolumeMap; } }; } catch (final Exception ex) { LOG.error(ex); Logs.extreme().error(ex, ex); } return Suppliers.ofInstance(Collections.<String, StorageVolume>emptyMap()); } private static final class VolumeUpdateTaskExpiryEventListener implements EventListener<ClockTick> { public static void register() { Listeners.register(ClockTick.class, new VolumeUpdateTaskExpiryEventListener()); } @Override public void fireEvent(final ClockTick event) { final long expiry = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5); for (final Map.Entry<String, Long> entry : pendingUpdates.entrySet()) { if (entry.getValue() < expiry) { if (pendingUpdates.remove(entry.getKey(), entry.getValue())) { LOG.warn("Expired update task for volume " + entry.getKey()); } } } } } }