Merge "Bug 451 - Fix netconf exception handling"
[controller.git] / opendaylight / netconf / netconf-client / src / main / java / org / opendaylight / controller / netconf / client / NetconfClientSessionNegotiator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.netconf.client;
10
11 import io.netty.channel.Channel;
12 import io.netty.channel.ChannelFuture;
13 import io.netty.channel.ChannelFutureListener;
14 import io.netty.channel.ChannelHandlerContext;
15 import io.netty.channel.ChannelInboundHandlerAdapter;
16 import io.netty.util.Timer;
17 import io.netty.util.concurrent.Promise;
18 import org.opendaylight.controller.netconf.api.NetconfClientSessionPreferences;
19 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
20 import org.opendaylight.controller.netconf.api.NetconfMessage;
21 import org.opendaylight.controller.netconf.util.AbstractChannelInitializer;
22 import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator;
23 import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
24 import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
25 import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
26 import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
27 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NodeList;
33
34 import javax.xml.xpath.XPathConstants;
35 import javax.xml.xpath.XPathExpression;
36
37 public class NetconfClientSessionNegotiator extends
38         AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession, NetconfClientSessionListener>
39 {
40     private static final Logger logger = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
41
42     private static final XPathExpression sessionIdXPath = XMLNetconfUtil
43             .compileXPath("/netconf:hello/netconf:session-id");
44
45     private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
46
47     protected NetconfClientSessionNegotiator(NetconfClientSessionPreferences sessionPreferences,
48                                              Promise<NetconfClientSession> promise,
49                                              Channel channel,
50                                              Timer timer,
51                                              NetconfClientSessionListener sessionListener,
52                                              long connectionTimeoutMillis) {
53         super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
54     }
55
56     @Override
57     protected void handleMessage(NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
58         NetconfClientSession session = super.getSessionForHelloMessage(netconfMessage);
59
60         if (shouldUseExi(netconfMessage.getDocument())){
61             logger.debug("Netconf session: {} should use exi.", session);
62             tryToStartExi(session);
63         } else {
64             logger.debug("Netconf session {} isn't capable using exi.", session);
65             negotiationSuccessful(session);
66         }
67     }
68
69     private boolean shouldUseExi(Document doc) {
70         return containsExi10Capability(doc)
71                 && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
72     }
73
74     private boolean containsExi10Capability(final Document doc) {
75         final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
76         for (int i = 0; i < nList.getLength(); i++) {
77             if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
78                 return true;
79             }
80         }
81         return false;
82     }
83
84     private void tryToStartExi(final NetconfClientSession session) {
85         final NetconfMessage startExi = sessionPreferences.getStartExiMessage();
86         session.sendMessage(startExi).addListener(new ChannelFutureListener() {
87             @Override
88             public void operationComplete(final ChannelFuture f) {
89                 if (!f.isSuccess()) {
90                     logger.warn("Failed to send start-exi message {} on session {}", startExi, session, f.cause());
91                 } else {
92                     logger.trace("Start-exi message {} sent to socket on session {}", startExi, session);
93                     NetconfClientSessionNegotiator.this.channel.pipeline().addAfter(
94                             AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
95                             new ExiConfirmationInboundHandler(session));
96                 }
97             }
98         });
99     }
100
101     private long extractSessionId(Document doc) {
102         final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
103         String textContent = sessionIdNode.getTextContent();
104         if (textContent == null || textContent.equals("")) {
105             throw new IllegalStateException("Session id not received from server");
106         }
107
108         return Long.valueOf(textContent);
109     }
110
111     @Override
112     protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfHelloMessage message) throws NetconfDocumentedException {
113         return new NetconfClientSession(sessionListener, channel, extractSessionId(message.getDocument()),
114                 NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument()));
115     }
116
117     /**
118      * Handler to process response for start-exi message
119      */
120     private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
121         private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
122
123         private final NetconfClientSession session;
124
125         ExiConfirmationInboundHandler(NetconfClientSession session) {
126             this.session = session;
127         }
128
129         @Override
130         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
131             ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
132
133             NetconfMessage netconfMessage = (NetconfMessage) msg;
134
135             // Ok response to start-exi, try to add exi handlers
136             if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
137                 logger.trace("Positive response on start-exi call received on session {}", session);
138                 try {
139                     session.startExiCommunication(sessionPreferences.getStartExiMessage());
140                 } catch (RuntimeException e) {
141                     // Unable to add exi, continue without exi
142                     logger.warn("Unable to start exi communication, Communication will continue without exi on session {}", session, e);
143                 }
144
145             // Error response
146             } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) {
147                 logger.warn(
148                         "Error response to start-exi message {}, Communication will continue without exi on session {}",
149                         XmlUtil.toString(netconfMessage.getDocument()), session);
150
151             // Unexpected response to start-exi, throwing message away, continue without exi
152             } else {
153                 logger.warn(
154                         "Unexpected response to start-exi message, should be ok, was {}, " +
155                                 "Communication will continue without exi and response message will be thrown away on session {}",
156                         XmlUtil.toString(netconfMessage.getDocument()), session);
157             }
158
159             negotiationSuccessful(session);
160         }
161     }
162 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.