package com.eucalyptus.cluster.handlers; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.jboss.netty.channel.WriteCompletionEvent; import org.jboss.netty.handler.codec.http.HttpChunkAggregator; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpRequestEncoder; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpVersion; import com.eucalyptus.auth.ClusterCredentials; import com.eucalyptus.binding.Binding; import com.eucalyptus.binding.BindingException; import com.eucalyptus.binding.BindingManager; import com.eucalyptus.cluster.Cluster; import com.eucalyptus.cluster.event.NewClusterEvent; import com.eucalyptus.cluster.event.TeardownClusterEvent; import com.eucalyptus.config.ClusterConfiguration; import com.eucalyptus.event.ClockTick; import com.eucalyptus.event.Event; import com.eucalyptus.event.EventListener; import com.eucalyptus.event.GenericEvent; import com.eucalyptus.http.MappingHttpRequest; import com.eucalyptus.http.MappingHttpResponse; import com.eucalyptus.records.EventType; import com.eucalyptus.util.EucalyptusClusterException; import com.eucalyptus.util.LogUtil; import com.eucalyptus.ws.client.NioBootstrap; import com.eucalyptus.ws.handlers.BindingHandler; import com.eucalyptus.ws.handlers.NioHttpResponseDecoder; import com.eucalyptus.ws.handlers.SoapMarshallingHandler; import com.eucalyptus.ws.handlers.soap.AddressingHandler; import com.eucalyptus.ws.handlers.soap.SoapHandler; import com.eucalyptus.ws.handlers.wssecurity.ClusterWsSecHandler; import com.eucalyptus.ws.util.ChannelUtil; import com.eucalyptus.records.EventRecord; import edu.ucsb.eucalyptus.msgs.GetKeysResponseType; public abstract class AbstractClusterMessageDispatcher extends SimpleChannelHandler implements ChannelPipelineFactory, EventListener { private static Logger LOG = Logger.getLogger( AbstractClusterMessageDispatcher.class ); private NioBootstrap clientBootstrap; private Cluster cluster; private Binding binding; private InetSocketAddress remoteAddr; private String hostName; private int port; private String servicePath; protected boolean verified = false; private String actionPrefix; private static String SECURE_NAME = "EucalyptusCC"; private static String SECURE_NC_NAME = "EucalyptusNC"; private static String INSECURE_NAME = "EucalyptusGL"; public AbstractClusterMessageDispatcher( Cluster cluster, boolean secure ) throws BindingException { this( cluster ); if ( !secure ) { this.servicePath = makeInsecure( this.servicePath ); this.actionPrefix = makeInsecure( this.actionPrefix ); } } protected static String makeInsecure( String input ) { return input.replaceAll( SECURE_NAME, INSECURE_NAME ).replaceAll( SECURE_NC_NAME, INSECURE_NAME ); } public AbstractClusterMessageDispatcher( Cluster cluster ) throws BindingException { this( ); this.cluster = cluster; ClusterConfiguration config = this.getCluster( ).getConfiguration( ); this.hostName = config.getHostName( ); this.port = config.getPort( ); this.servicePath = config.getServicePath( ); this.actionPrefix = SECURE_NAME; this.remoteAddr = new InetSocketAddress( cluster.getConfiguration( ).getHostName( ), cluster.getConfiguration( ).getPort( ) ); } private AbstractClusterMessageDispatcher( ) throws BindingException { this.clientBootstrap = ChannelUtil.getClientBootstrap( this ); this.binding = BindingManager.getBinding( "eucalyptus_ucsb_edu" ); } public abstract void trigger( ); public abstract void upstreamMessage( ChannelHandlerContext ctx, MessageEvent e ); @Override public ChannelPipeline getPipeline( ) throws Exception {//FIXME: remove all these repetitions. ChannelPipeline pipeline = Channels.pipeline( ); ChannelUtil.addPipelineMonitors( pipeline, 30 ); pipeline.addLast( "encoder", new HttpRequestEncoder( ) ); pipeline.addLast( "decoder", new NioHttpResponseDecoder( ) ); pipeline.addLast( "aggregator", new HttpChunkAggregator( 1048576 ) ); pipeline.addLast( "serializer", new SoapMarshallingHandler( ) ); pipeline.addLast( "wssec", new ClusterWsSecHandler( ) ); pipeline.addLast( "addressing", new AddressingHandler( this.actionPrefix + "#" ) ); pipeline.addLast( "soap", new SoapHandler( ) ); pipeline.addLast( "binding", new BindingHandler( this.binding ) ); pipeline.addLast( "handler", this ); return pipeline; } private AtomicBoolean inFlightMessage = new AtomicBoolean( false ); public void write( final Object o ) { if( !this.inFlightMessage.compareAndSet( false, true ) ) { LOG.trace( EventRecord.caller( AbstractClusterMessageDispatcher.class, EventType.MSG_REJECTED, LogUtil.dumpObject( o ) ) ); return; } else { ChannelFuture channelConnectFuture = this.clientBootstrap.connect( this.remoteAddr ); final HttpRequest request = new MappingHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.POST, this.hostName, this.port, this.servicePath, o ); channelConnectFuture.addListener( ChannelFutureListener.CLOSE_ON_FAILURE ); channelConnectFuture.addListener( ChannelUtil.WRITE_AND_CALLBACK( request, new ChannelFutureListener( ) { @Override public void operationComplete( ChannelFuture future ) throws Exception { EventRecord.here( o.getClass(), EventType.MSG_SENT, LogUtil.dumpObject( o ) ).info( ); } } ) ); } } protected void fireTimedStatefulTrigger( Event event ) { if ( this.timedTrigger( event ) ) { this.trigger( ); } else if ( event instanceof GenericEvent ) { GenericEvent<Cluster> g = ( GenericEvent<Cluster> ) event; if ( !g.matches( this.getCluster( ) ) ) { this.inFlightMessage.set( false ); return; } if ( g instanceof NewClusterEvent && !this.verified ) { this.trigger( ); this.inFlightMessage.set( false ); } else if ( event instanceof TeardownClusterEvent ) { this.verified = false; this.inFlightMessage.set( false ); } } } private void clearPending( Channel channel ) { channel.close( ); this.inFlightMessage.set( false ); } @Override public void channelInterestChanged( ChannelHandlerContext ctx, ChannelStateEvent e ) throws Exception { EventRecord.here( AbstractClusterMessageDispatcher.class, EventType.MSG_PENDING, e.toString( ) ).trace( ); super.channelInterestChanged( ctx, e ); } @Override public void messageReceived( ChannelHandlerContext ctx, MessageEvent e ) throws Exception { try { if ( e.getMessage( ) instanceof MappingHttpResponse ) { MappingHttpResponse response = ( MappingHttpResponse ) ( ( MessageEvent ) e ).getMessage( ); if ( HttpResponseStatus.OK.equals( response.getStatus( ) ) ) { if(!( response.getMessage( ) instanceof GetKeysResponseType )) { EventRecord.here( response.getMessage( ).getClass(), EventType.MSG_SENT, LogUtil.dumpObject( response.getMessage( ) ) ).info( ); } this.upstreamMessage( ctx, ( MessageEvent ) e ); } else { throw new EucalyptusClusterException( response.getMessageString( ) ); } } } finally { this.clearPending( ctx.getChannel( ) ); } super.messageReceived( ctx, e ); } @Override public void writeComplete( ChannelHandlerContext ctx, WriteCompletionEvent e ) throws Exception { EventRecord.here( AbstractClusterMessageDispatcher.class, EventType.MSG_SERVICED, e.toString( ) ).trace( ); super.writeComplete( ctx, e ); } @Override public void channelClosed( ChannelHandlerContext ctx, ChannelStateEvent e ) throws Exception { this.inFlightMessage.set( false ); super.channelClosed( ctx, e ); } @Override public void writeRequested( ChannelHandlerContext ctx, MessageEvent e ) throws Exception { LOG.trace( EventRecord.here( AbstractClusterMessageDispatcher.class, EventType.MSG_PENDING, e.getMessage( ).toString( ) ) ); super.writeRequested( ctx, e ); } @Override public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e ) throws Exception { if(this.inFlightMessage.get( )) { if( e != null && e.getCause() != null ) { LOG.debug( e.getCause( ), e.getCause( ) ); } else { Exception ex = new RuntimeException("Exception even has a null-valued cause."); LOG.error( ex, ex ); } this.clearPending( ctx.getChannel( ) ); } } public Cluster getCluster( ) { return cluster; } public ClusterConfiguration getConfiguration( ) { return cluster.getConfiguration( ); } public ClusterCredentials getCredentials( ) { return cluster.getCredentials( ); } public String getName( ) { return cluster.getName( ); } public void setCluster( Cluster cluster ) { this.cluster = cluster; } public boolean timedTrigger( Event c ) { if ( c instanceof ClockTick ) { return ( ( ClockTick ) c ).isBackEdge( ); } else { return false; } } @Override public int hashCode( ) { final int prime = 31; int result = 1; result = prime * result + ( ( this.cluster == null ) ? 0 : this.cluster.hashCode( ) ); return result; } @Override public boolean equals( Object obj ) { if ( this == obj ) return true; if ( obj == null ) return false; if ( getClass( ) != obj.getClass( ) ) return false; AbstractClusterMessageDispatcher other = ( AbstractClusterMessageDispatcher ) obj; if ( this.cluster == null ) { if ( other.cluster != null ) return false; } else if ( !this.cluster.getName( ).equals( other.cluster.getName( ) ) ) return false; return true; } @Override public void advertiseEvent( Event event ) {} @Override public void fireEvent( Event event ) {} }