Merge "Bug 509: Improve logging in InMemoryDataStore."
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / AbstractNetconfSessionNegotiator.java
index a485a4ea9436391d7433e91370f37481734d4e7b..724b45bc08d867f6702ae75a11aece291fefd484 100644 (file)
@@ -13,6 +13,7 @@ import com.google.common.base.Preconditions;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandler;
 import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.ssl.SslHandler;
 import io.netty.util.Timeout;
 import io.netty.util.Timer;
@@ -21,17 +22,16 @@ import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GenericFutureListener;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.controller.netconf.api.NetconfMessage;
-import org.opendaylight.controller.netconf.api.NetconfSession;
+import org.opendaylight.controller.netconf.api.NetconfSessionListener;
 import org.opendaylight.controller.netconf.api.NetconfSessionPreferences;
 import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory;
-import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator;
-import org.opendaylight.controller.netconf.util.handler.NetconfMessageChunkDecoder;
+import org.opendaylight.controller.netconf.util.handler.NetconfChunkAggregator;
+import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder;
+import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder;
 import org.opendaylight.controller.netconf.util.messages.FramingMechanism;
-import org.opendaylight.controller.netconf.util.xml.XmlElement;
-import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
-import org.opendaylight.protocol.framework.SessionListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -39,21 +39,22 @@ import org.w3c.dom.NodeList;
 
 import java.util.concurrent.TimeUnit;
 
-public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionPreferences, S extends NetconfSession>
-        extends AbstractSessionNegotiator<NetconfMessage, S> {
+public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionPreferences, S extends AbstractNetconfSession<S, L>, L extends NetconfSessionListener<S>>
+extends AbstractSessionNegotiator<NetconfHelloMessage, S> {
 
     private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSessionNegotiator.class);
+
     public static final String NAME_OF_EXCEPTION_HANDLER = "lastExceptionHandler";
 
     protected final P sessionPreferences;
 
-    private final SessionListener sessionListener;
+    private final L sessionListener;
     private Timeout timeout;
 
     /**
      * Possible states for Finite State Machine
      */
-    private enum State {
+    protected enum State {
         IDLE, OPEN_WAIT, FAILED, ESTABLISHED
     }
 
@@ -61,8 +62,9 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
     private final Timer timer;
     private final long connectionTimeoutMillis;
 
+    // TODO shrink constructor
     protected AbstractNetconfSessionNegotiator(P sessionPreferences, Promise<S> promise, Channel channel, Timer timer,
-            SessionListener sessionListener, long connectionTimeoutMillis) {
+            L sessionListener, long connectionTimeoutMillis) {
         super(promise, channel);
         this.sessionPreferences = sessionPreferences;
         this.timer = timer;
@@ -77,14 +79,15 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
             Future<Channel> future = sslHandler.get().handshakeFuture();
             future.addListener(new GenericFutureListener<Future<? super Channel>>() {
                 @Override
-                public void operationComplete(Future<? super Channel> future) throws Exception {
+                public void operationComplete(Future<? super Channel> future) {
                     Preconditions.checkState(future.isSuccess(), "Ssl handshake was not successful");
                     logger.debug("Ssl handshake complete");
                     start();
                 }
             });
-        } else
+        } else {
             start();
+        }
     }
 
     private static Optional<SslHandler> getSslHandler(Channel channel) {
@@ -92,31 +95,19 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
         return sslHandler == null ? Optional.<SslHandler> absent() : Optional.of(sslHandler);
     }
 
+    public P getSessionPreferences() {
+        return sessionPreferences;
+    }
+
     private void start() {
         final NetconfMessage helloMessage = this.sessionPreferences.getHelloMessage();
         logger.debug("Session negotiation started with hello message {}", XmlUtil.toString(helloMessage.getDocument()));
 
-        channel.pipeline().addLast(NAME_OF_EXCEPTION_HANDLER, new ChannelHandler() {
-            @Override
-            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
-            }
-
-            @Override
-            public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
-            }
-
-            @Override
-            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
-                logger.warn("An exception occurred during negotiation on channel {}", channel.localAddress(), cause);
-                cancelTimeout();
-                negotiationFailed(cause);
-                changeState(State.FAILED);
-            }
-        });
+        channel.pipeline().addLast(NAME_OF_EXCEPTION_HANDLER, new ExceptionHandlingInboundChannelHandler());
 
         timeout = this.timer.newTimeout(new TimerTask() {
             @Override
-            public void run(final Timeout timeout) throws Exception {
+            public void run(final Timeout timeout) {
                 synchronized (this) {
                     if (state != State.ESTABLISHED) {
                         logger.debug("Connection timeout after {}, session is in state {}", timeout, state);
@@ -131,57 +122,67 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
             }
         }, connectionTimeoutMillis, TimeUnit.MILLISECONDS);
 
-        sendMessage(helloMessage);
+        // FIXME, make sessionPreferences return HelloMessage, move NetconfHelloMessage to API
+        sendMessage((NetconfHelloMessage)helloMessage);
         changeState(State.OPEN_WAIT);
     }
-
     private void cancelTimeout() {
-        if(timeout!=null)
+        if(timeout!=null) {
             timeout.cancel();
+        }
     }
 
-    private void sendMessage(NetconfMessage message) {
-        this.channel.writeAndFlush(message);
+    @Override
+    protected void handleMessage(NetconfHelloMessage netconfMessage) {
+        S session = getSessionForHelloMessage(netconfMessage);
+        negotiationSuccessful(session);
     }
 
-    @Override
-    protected void handleMessage(NetconfMessage netconfMessage) {
+    protected final S getSessionForHelloMessage(NetconfHelloMessage netconfMessage) {
+        Preconditions.checkNotNull(netconfMessage, "netconfMessage");
+
         final Document doc = netconfMessage.getDocument();
 
-        if (isHelloMessage(doc)) {
-            if (containsBase11Capability(doc)
-                    && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument())) {
-                channel.pipeline().replace("frameEncoder", "frameEncoder",
-                        FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK));
-                channel.pipeline().replace("aggregator", "aggregator",
-                        new NetconfMessageAggregator(FramingMechanism.CHUNK));
-                channel.pipeline().addAfter("aggregator", "chunkDecoder", new NetconfMessageChunkDecoder());
-            }
-            changeState(State.ESTABLISHED);
-            S session = getSession(sessionListener, channel, netconfMessage);
-            negotiationSuccessful(session);
-        } else {
-            final IllegalStateException cause = new IllegalStateException(
-                    "Received message was not hello as expected, but was " + XmlUtil.toString(doc));
-            logger.warn("Negotiation of netconf session failed", cause);
-            negotiationFailed(cause);
+        replaceHelloMessageHandlers();
+
+        if (shouldUseChunkFraming(doc)) {
+            insertChunkFramingToPipeline();
         }
+
+        changeState(State.ESTABLISHED);
+        return getSession(sessionListener, channel, netconfMessage);
     }
 
-    protected abstract S getSession(SessionListener sessionListener, Channel channel, NetconfMessage message);
+    /**
+     * Insert chunk framing handlers into the pipeline
+     */
+    protected void insertChunkFramingToPipeline() {
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_FRAME_ENCODER,
+                FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK));
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR,
+                new NetconfChunkAggregator());
+    }
 
-    private boolean isHelloMessage(Document doc) {
-        try {
-            XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), "hello",
-                    XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
+    protected boolean shouldUseChunkFraming(Document doc) {
+        return containsBase11Capability(doc)
+                && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument());
+    }
 
-        } catch (IllegalArgumentException | IllegalStateException e) {
-            return false;
-        }
-        return true;
+    /**
+     * Remove special handlers for hello message. Insert regular netconf xml message (en|de)coders.
+     */
+    protected void replaceHelloMessageHandlers() {
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, new NetconfXMLToMessageDecoder());
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, new NetconfMessageToXMLEncoder());
     }
 
-    private synchronized void changeState(final State newState) {
+    private static ChannelHandler replaceChannelHandler(Channel channel, String handlerKey, ChannelHandler decoder) {
+        return channel.pipeline().replace(handlerKey, handlerKey, decoder);
+    }
+
+    protected abstract S getSession(L sessionListener, Channel channel, NetconfHelloMessage message);
+
+    protected synchronized void changeState(final State newState) {
         logger.debug("Changing state from : {} to : {}", state, newState);
         Preconditions.checkState(isStateChangePermitted(state, newState), "Cannot change state from %s to %s", state,
                 newState);
@@ -199,14 +200,29 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
     }
 
     private static boolean isStateChangePermitted(State state, State newState) {
-        if (state == State.IDLE && newState == State.OPEN_WAIT)
+        if (state == State.IDLE && newState == State.OPEN_WAIT) {
             return true;
-        if (state == State.OPEN_WAIT && newState == State.ESTABLISHED)
+        }
+        if (state == State.OPEN_WAIT && newState == State.ESTABLISHED) {
             return true;
-        if (state == State.OPEN_WAIT && newState == State.FAILED)
+        }
+        if (state == State.OPEN_WAIT && newState == State.FAILED) {
             return true;
-
+        }
         logger.debug("Transition from {} to {} is not allowed", state, newState);
         return false;
     }
+
+    /**
+     * Handler to catch exceptions in pipeline during negotiation
+     */
+    private final class ExceptionHandlingInboundChannelHandler extends ChannelInboundHandlerAdapter {
+        @Override
+        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+            logger.warn("An exception occurred during negotiation on channel {}", channel.localAddress(), cause);
+            cancelTimeout();
+            negotiationFailed(cause);
+            changeState(State.FAILED);
+        }
+    }
 }