package com.eucalyptus.cluster; import java.util.concurrent.ConcurrentLinkedQueue; import org.apache.log4j.Logger; import com.eucalyptus.cluster.callback.BroadcastCallback; import com.eucalyptus.cluster.callback.QueuedEventCallback; import com.eucalyptus.records.EventType; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.eucalyptus.records.EventRecord; public class StatefulMessageSet<E extends Enum<E>> { private static Logger LOG = Logger.getLogger( StatefulMessageSet.class ); private Multimap<E, QueuedEventCallback> messages = Multimaps.newHashMultimap( ); private ConcurrentLinkedQueue<QueuedEventCallback> pendingEvents = new ConcurrentLinkedQueue<QueuedEventCallback>( ); private E[] states; private E state; private E endState; private E failState; private Cluster cluster; private Long startTime; /** * Collection of messages which need to honor a certain ordering. The state array is a list of enum values where: * <ul> * <li>Index 0: is the start state</li> * <li>Index length-2: is the end state</li> * <li>Index length-1: is the rollback state</li> * </ul> * A transition will increase the currentState's ordinal by 1, drain the messages to a pending queue and wait for all messages to be serviced before * proceeding. * * @param cluster * @param states */ public StatefulMessageSet( Cluster cluster, E[] states ) { this.cluster = cluster; this.states = states; this.state = states[0]; this.endState = states[states.length - 2]; this.failState = states[states.length - 1]; this.startTime = System.currentTimeMillis( ); } private E rollback( ) { return ( this.state = failState ); } public void addRequest( E state, QueuedEventCallback callback ) { EventRecord.caller( StatefulMessageSet.class, EventType.VM_PREPARE, state.name( ), callback.getClass( ).getSimpleName( ) ).debug( ); this.messages.put( state, callback ); } @SuppressWarnings( "unchecked" ) private void queueEvents( final E state ) { for ( final QueuedEventCallback event : this.messages.get( state ) ) { if ( event instanceof BroadcastCallback ) { final BroadcastCallback callback = ( BroadcastCallback ) event; this.pendingEvents.addAll( Lists.transform( Clusters.getInstance( ).listValues( ), new Function<Cluster, QueuedEventCallback>( ) { public QueuedEventCallback apply( Cluster c ) { EventRecord.caller( StatefulMessageSet.class, EventType.VM_STARTING, state.name( ), c.getName( ), event.getClass( ).getSimpleName( ) ).info( ); return callback.newInstance( ).regardingUserRequest( callback.getRequest( ) ).dispatch( c ); } } ) ); } else { this.pendingEvents.add( event.dispatch( cluster ) ); EventRecord.caller( StatefulMessageSet.class, EventType.VM_STARTING, state.name( ), cluster.getName( ), event.getClass( ).getSimpleName( ) ).info( ); } } } private E transition( final E currentState ) { QueuedEventCallback event = null; E nextState = this.states[currentState.ordinal( ) + 1]; while ( ( event = this.pendingEvents.poll( ) ) != null ) { while ( !event.pollForResponse( 100l ) ); try { Object o = event.pollResponse( 100l ); if ( o != null ) { EventRecord.here( StatefulMessageSet.class, EventType.VM_STARTING, currentState.name( ), cluster.getName( ), o.getClass( ).getSimpleName( ) ).info( ); } } catch ( Throwable t ) { EventRecord.here( StatefulMessageSet.class, EventType.VM_STARTING, currentState.name( ), cluster.getName( ), t.getClass( ).getSimpleName( ) ).info( ); LOG.debug( t, t ); nextState = this.rollback( ); } } EventRecord.here( StatefulMessageSet.class, EventType.VM_STARTING, currentState.name( ), EventType.TRANSITION.name( ), nextState.name( ) ).info( ); return nextState; } private boolean isSuccessful( ) { return this.state.equals( endState ); } private boolean isFinished( ) { return this.state.equals( this.failState ) || this.state.equals( endState ); } public void run( ) { do { this.queueEvents( this.state ); this.state = this.transition( this.state ); } while ( !this.isFinished( ) ); LOG.info( EventRecord.here( StatefulMessageSet.class, this.isSuccessful( )?EventType.VM_START_COMPLETED:EventType.VM_START_ABORTED, ( System.currentTimeMillis( ) - this.startTime ) / 1000.0d + "s" ) ); } }