Merge "Bug 451 - Fix netconf exception handling"
[controller.git] / opendaylight / netconf / netconf-client / src / main / java / org / opendaylight / controller / netconf / client / NetconfClientSessionNegotiator.java
index 100b98c15af18e1791dc4c43fcc460189ee4384e..f8f73fc8e5296b9534e0759bc43f24bbf493521e 100644 (file)
@@ -8,56 +8,96 @@
 
 package org.opendaylight.controller.netconf.client;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
 import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.util.Timer;
 import io.netty.util.concurrent.Promise;
+import org.opendaylight.controller.netconf.api.NetconfClientSessionPreferences;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
 import org.opendaylight.controller.netconf.api.NetconfMessage;
-import org.opendaylight.controller.netconf.api.NetconfSessionPreferences;
+import org.opendaylight.controller.netconf.util.AbstractChannelInitializer;
 import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
 import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
-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.SessionListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
-import javax.annotation.Nullable;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpression;
-import java.util.Collection;
-import java.util.List;
 
 public class NetconfClientSessionNegotiator extends
-        AbstractNetconfSessionNegotiator<NetconfSessionPreferences, NetconfClientSession> {
+        AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession, NetconfClientSessionListener>
+{
+    private static final Logger logger = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
 
-    protected NetconfClientSessionNegotiator(NetconfSessionPreferences sessionPreferences,
-            Promise<NetconfClientSession> promise, Channel channel, Timer timer, SessionListener sessionListener,
-            long connectionTimeoutMillis) {
+    private static final XPathExpression sessionIdXPath = XMLNetconfUtil
+            .compileXPath("/netconf:hello/netconf:session-id");
+
+    private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
+
+    protected NetconfClientSessionNegotiator(NetconfClientSessionPreferences sessionPreferences,
+                                             Promise<NetconfClientSession> promise,
+                                             Channel channel,
+                                             Timer timer,
+                                             NetconfClientSessionListener sessionListener,
+                                             long connectionTimeoutMillis) {
         super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
     }
 
-    private static Collection<String> getCapabilities(Document doc) {
-        XmlElement responseElement = XmlElement.fromDomDocument(doc);
-        XmlElement capabilitiesElement = responseElement
-                .getOnlyChildElementWithSameNamespace(XmlNetconfConstants.CAPABILITIES);
-        List<XmlElement> caps = capabilitiesElement.getChildElements(XmlNetconfConstants.CAPABILITY);
-        return Collections2.transform(caps, new Function<XmlElement, String>() {
+    @Override
+    protected void handleMessage(NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
+        NetconfClientSession session = super.getSessionForHelloMessage(netconfMessage);
+
+        if (shouldUseExi(netconfMessage.getDocument())){
+            logger.debug("Netconf session: {} should use exi.", session);
+            tryToStartExi(session);
+        } else {
+            logger.debug("Netconf session {} isn't capable using exi.", session);
+            negotiationSuccessful(session);
+        }
+    }
 
-            @Nullable
+    private boolean shouldUseExi(Document doc) {
+        return containsExi10Capability(doc)
+                && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
+    }
+
+    private boolean containsExi10Capability(final Document doc) {
+        final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
+        for (int i = 0; i < nList.getLength(); i++) {
+            if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void tryToStartExi(final NetconfClientSession session) {
+        final NetconfMessage startExi = sessionPreferences.getStartExiMessage();
+        session.sendMessage(startExi).addListener(new ChannelFutureListener() {
             @Override
-            public String apply(@Nullable XmlElement input) {
-                // Trim possible leading/tailing whitespace
-                return input.getTextContent().trim();
+            public void operationComplete(final ChannelFuture f) {
+                if (!f.isSuccess()) {
+                    logger.warn("Failed to send start-exi message {} on session {}", startExi, session, f.cause());
+                } else {
+                    logger.trace("Start-exi message {} sent to socket on session {}", startExi, session);
+                    NetconfClientSessionNegotiator.this.channel.pipeline().addAfter(
+                            AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
+                            new ExiConfirmationInboundHandler(session));
+                }
             }
         });
     }
 
-    private static final XPathExpression sessionIdXPath = XMLNetconfUtil
-            .compileXPath("/netconf:hello/netconf:session-id");
-
     private long extractSessionId(Document doc) {
         final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
         String textContent = sessionIdNode.getTextContent();
@@ -69,8 +109,54 @@ public class NetconfClientSessionNegotiator extends
     }
 
     @Override
-    protected NetconfClientSession getSession(SessionListener sessionListener, Channel channel, NetconfMessage message) {
+    protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfHelloMessage message) throws NetconfDocumentedException {
         return new NetconfClientSession(sessionListener, channel, extractSessionId(message.getDocument()),
-                getCapabilities(message.getDocument()));
+                NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument()));
+    }
+
+    /**
+     * Handler to process response for start-exi message
+     */
+    private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
+        private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
+
+        private final NetconfClientSession session;
+
+        ExiConfirmationInboundHandler(NetconfClientSession session) {
+            this.session = session;
+        }
+
+        @Override
+        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+            ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
+
+            NetconfMessage netconfMessage = (NetconfMessage) msg;
+
+            // Ok response to start-exi, try to add exi handlers
+            if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
+                logger.trace("Positive response on start-exi call received on session {}", session);
+                try {
+                    session.startExiCommunication(sessionPreferences.getStartExiMessage());
+                } catch (RuntimeException e) {
+                    // Unable to add exi, continue without exi
+                    logger.warn("Unable to start exi communication, Communication will continue without exi on session {}", session, e);
+                }
+
+            // Error response
+            } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) {
+                logger.warn(
+                        "Error response to start-exi message {}, Communication will continue without exi on session {}",
+                        XmlUtil.toString(netconfMessage.getDocument()), session);
+
+            // Unexpected response to start-exi, throwing message away, continue without exi
+            } else {
+                logger.warn(
+                        "Unexpected response to start-exi message, should be ok, was {}, " +
+                                "Communication will continue without exi and response message will be thrown away on session {}",
+                        XmlUtil.toString(netconfMessage.getDocument()), session);
+            }
+
+            negotiationSuccessful(session);
+        }
     }
 }