Merge "CHange log level from warn to debug in ProtocolSessionPromise when connection...
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / AbstractNetconfSessionNegotiator.java
index e30ce5b47e961f87adccebcce363f2092da9c165..9986b82bd8d6902a084e77a1a11a6421125ccb17 100644 (file)
@@ -8,49 +8,50 @@
 
 package org.opendaylight.controller.netconf.util;
 
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.ssl.SslHandler;
-import io.netty.util.Timeout;
-import io.netty.util.Timer;
-import io.netty.util.TimerTask;
-import io.netty.util.concurrent.Future;
-import io.netty.util.concurrent.GenericFutureListener;
-import io.netty.util.concurrent.Promise;
+import java.util.concurrent.TimeUnit;
+
+import org.opendaylight.controller.netconf.api.AbstractNetconfSession;
 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.messages.NetconfHelloMessage;
 import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator;
 import org.opendaylight.controller.netconf.util.handler.NetconfMessageChunkDecoder;
+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.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;
 import org.w3c.dom.NodeList;
 
-import java.util.concurrent.TimeUnit;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 
-public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionPreferences, S extends NetconfSession>
-        extends AbstractSessionNegotiator<NetconfMessage, S> {
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.Timeout;
+import io.netty.util.Timer;
+import io.netty.util.TimerTask;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import io.netty.util.concurrent.Promise;
 
-    // TODO what time ?
-    private static final long INITIAL_HOLDTIMER = 1;
+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";
+    public static final String CHUNK_DECODER_CHANNEL_HANDLER_KEY = "chunkDecoder";
 
     protected final P sessionPreferences;
 
-    private final SessionListener sessionListener;
+    private final L sessionListener;
     private Timeout timeout;
 
     /**
@@ -62,13 +63,15 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
 
     private State state = State.IDLE;
     private final Timer timer;
+    private final long connectionTimeoutMillis;
 
     protected AbstractNetconfSessionNegotiator(P sessionPreferences, Promise<S> promise, Channel channel, Timer timer,
-            SessionListener sessionListener) {
+            L sessionListener, long connectionTimeoutMillis) {
         super(promise, channel);
         this.sessionPreferences = sessionPreferences;
         this.timer = timer;
         this.sessionListener = sessionListener;
+        this.connectionTimeoutMillis = connectionTimeoutMillis;
     }
 
     @Override
@@ -120,15 +123,17 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
             public void run(final Timeout timeout) throws Exception {
                 synchronized (this) {
                     if (state != State.ESTABLISHED) {
+                        logger.debug("Connection timeout after {}, session is in state {}", timeout, state);
                         final IllegalStateException cause = new IllegalStateException(
                                 "Session was not established after " + timeout);
                         negotiationFailed(cause);
                         changeState(State.FAILED);
-                    } else
+                    } else if(channel.isOpen()) {
                         channel.pipeline().remove(NAME_OF_EXCEPTION_HANDLER);
+                    }
                 }
             }
-        }, INITIAL_HOLDTIMER, TimeUnit.MINUTES);
+        }, connectionTimeoutMillis, TimeUnit.MILLISECONDS);
 
         sendMessage(helloMessage);
         changeState(State.OPEN_WAIT);
@@ -144,20 +149,21 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
     }
 
     @Override
-    protected void handleMessage(NetconfMessage netconfMessage) {
+    protected void handleMessage(NetconfHelloMessage 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());
+        // Only Hello message should arrive during negotiation
+        if (netconfMessage instanceof NetconfHelloMessage) {
+
+            replaceHelloMessageHandlers();
+
+            if (shouldUseChunkFraming(doc)) {
+                insertChunkFramingToPipeline();
             }
+
             changeState(State.ESTABLISHED);
-            S session = getSession(sessionListener, channel, netconfMessage);
+            S session = getSession(sessionListener, channel, (NetconfHelloMessage)netconfMessage);
+
             negotiationSuccessful(session);
         } else {
             final IllegalStateException cause = new IllegalStateException(
@@ -167,20 +173,38 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
         }
     }
 
-    protected abstract S getSession(SessionListener sessionListener, Channel channel, NetconfMessage message);
+    /**
+     * Insert chunk framing handlers into the pipeline
+     */
+    private void insertChunkFramingToPipeline() {
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_FRAME_ENCODER,
+                FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK));
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR,
+                new NetconfMessageAggregator(FramingMechanism.CHUNK));
+        channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR,
+                CHUNK_DECODER_CHANNEL_HANDLER_KEY, new NetconfMessageChunkDecoder());
+    }
 
-    private boolean isHelloMessage(Document doc) {
-        try {
-            XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), "hello",
-                    XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
+    private 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.
+     */
+    private void replaceHelloMessageHandlers() {
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, new NetconfXMLToMessageDecoder());
+        replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, new NetconfMessageToXMLEncoder());
     }
 
-    private 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);
+
+    private 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);
@@ -205,6 +229,7 @@ public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionP
         if (state == State.OPEN_WAIT && newState == State.FAILED)
             return true;
 
+        logger.debug("Transition from {} to {} is not allowed", state, newState);
         return false;
     }
 }