Added channel to logs in NETCONF negotiator
[netconf.git] / netconf / netconf-client / src / main / java / org / opendaylight / 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.netconf.client;
10
11 import com.google.common.base.Strings;
12 import com.google.common.collect.ImmutableSet;
13 import com.google.common.collect.Interner;
14 import com.google.common.collect.Interners;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import io.netty.channel.Channel;
17 import io.netty.channel.ChannelFuture;
18 import io.netty.channel.ChannelFutureListener;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.channel.ChannelInboundHandlerAdapter;
21 import io.netty.util.Timer;
22 import io.netty.util.concurrent.Promise;
23 import java.util.Set;
24 import javax.xml.xpath.XPathConstants;
25 import javax.xml.xpath.XPathExpression;
26 import org.opendaylight.netconf.api.NetconfClientSessionPreferences;
27 import org.opendaylight.netconf.api.NetconfDocumentedException;
28 import org.opendaylight.netconf.api.NetconfMessage;
29 import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
30 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
31 import org.opendaylight.netconf.api.xml.XmlUtil;
32 import org.opendaylight.netconf.nettyutil.AbstractChannelInitializer;
33 import org.opendaylight.netconf.nettyutil.AbstractNetconfSessionNegotiator;
34 import org.opendaylight.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
35 import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
36 import org.opendaylight.netconf.util.xml.XMLNetconfUtil;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Node;
41 import org.w3c.dom.NodeList;
42
43 public class NetconfClientSessionNegotiator extends
44         AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession,
45                 NetconfClientSessionListener> {
46     private static final Logger LOG = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
47
48     private static final XPathExpression SESSION_ID_X_PATH = XMLNetconfUtil
49             .compileXPath("/netconf:hello/netconf:session-id");
50
51     private static final XPathExpression SESSION_ID_X_PATH_NO_NAMESPACE = XMLNetconfUtil
52             .compileXPath("/hello/session-id");
53
54     private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
55
56     private static final Interner<Set<String>> INTERNER = Interners.newWeakInterner();
57
58     protected NetconfClientSessionNegotiator(final NetconfClientSessionPreferences sessionPreferences,
59                                              final Promise<NetconfClientSession> promise,
60                                              final Channel channel,
61                                              final Timer timer,
62                                              final NetconfClientSessionListener sessionListener,
63                                              final long connectionTimeoutMillis) {
64         super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
65     }
66
67     @SuppressWarnings("checkstyle:IllegalCatch")
68     @Override
69     @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
70     protected void handleMessage(final NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
71         if (!ifNegotiatedAlready()) {
72             LOG.debug("Server hello message received, starting negotiation on channel {}", channel);
73             try {
74                 startNegotiation();
75             } catch (final Exception e) {
76                 LOG.warn("Unexpected negotiation failure on channel {}", channel, e);
77                 negotiationFailed(e);
78                 return;
79             }
80         }
81         final NetconfClientSession session = getSessionForHelloMessage(netconfMessage);
82         replaceHelloMessageInboundHandler(session);
83
84         // If exi should be used, try to initiate exi communication
85         // Call negotiationSuccessFul after exi negotiation is finished successfully or not
86         final NetconfMessage startExiMessage = sessionPreferences.getStartExiMessage();
87         if (shouldUseExi(netconfMessage) && startExiMessage instanceof NetconfStartExiMessage) {
88             LOG.debug("Netconf session {} should use exi.", session);
89             tryToInitiateExi(session, (NetconfStartExiMessage) startExiMessage);
90         } else {
91             // Exi is not supported, release session immediately
92             LOG.debug("Netconf session {} isn't capable of using exi.", session);
93             negotiationSuccessful(session);
94         }
95     }
96
97     /**
98      * Initiates exi communication by sending start-exi message and waiting for positive/negative response.
99      *
100      * @param startExiMessage Exi message for initilization of exi communication.
101      */
102     void tryToInitiateExi(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
103         channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_DECODER,
104                 ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
105                 new ExiConfirmationInboundHandler(session, startExiMessage));
106
107         session.sendMessage(startExiMessage).addListener(new ChannelFutureListener() {
108             @Override
109             public void operationComplete(final ChannelFuture channelFuture) {
110                 if (!channelFuture.isSuccess()) {
111                     LOG.warn("Failed to send start-exi message {} on session {}", startExiMessage, this,
112                             channelFuture.cause());
113                     channel.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
114                 } else {
115                     LOG.trace("Start-exi message {} sent to socket on session {}", startExiMessage, this);
116                 }
117             }
118         });
119     }
120
121     @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
122     private boolean shouldUseExi(final NetconfHelloMessage helloMsg) {
123         return containsExi10Capability(helloMsg.getDocument())
124                 && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
125     }
126
127     private static boolean containsExi10Capability(final Document doc) {
128         final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
129         for (int i = 0; i < nList.getLength(); i++) {
130             if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
131                 return true;
132             }
133         }
134         return false;
135     }
136
137     private static long extractSessionId(final Document doc) {
138         String textContent = getSessionIdWithXPath(doc, SESSION_ID_X_PATH);
139         if (Strings.isNullOrEmpty(textContent)) {
140             textContent = getSessionIdWithXPath(doc, SESSION_ID_X_PATH_NO_NAMESPACE);
141             if (Strings.isNullOrEmpty(textContent)) {
142                 throw new IllegalStateException("Session id not received from server, hello message: " + XmlUtil
143                         .toString(doc));
144             }
145         }
146
147         return Long.parseLong(textContent);
148     }
149
150     private static String getSessionIdWithXPath(final Document doc, final XPathExpression sessionIdXPath) {
151         final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
152         return sessionIdNode != null ? sessionIdNode.getTextContent() : null;
153     }
154
155     @Override
156     protected NetconfClientSession getSession(final NetconfClientSessionListener sessionListener, final Channel channel,
157                                               final NetconfHelloMessage message) {
158         final long sessionId = extractSessionId(message.getDocument());
159
160         // Copy here is important: it disconnects the strings from the document
161         Set<String> capabilities = ImmutableSet.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message
162                 .getDocument()));
163
164         capabilities = INTERNER.intern(capabilities);
165
166         return new NetconfClientSession(sessionListener, channel, sessionId, capabilities);
167     }
168
169     /**
170      * Handler to process response for start-exi message.
171      */
172     private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
173         private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
174
175         private final NetconfClientSession session;
176         private final NetconfStartExiMessage startExiMessage;
177
178         ExiConfirmationInboundHandler(final NetconfClientSession session,
179                                       final NetconfStartExiMessage startExiMessage) {
180             this.session = session;
181             this.startExiMessage = startExiMessage;
182         }
183
184         @SuppressWarnings("checkstyle:IllegalCatch")
185         @Override
186         public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
187             ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
188
189             NetconfMessage netconfMessage = (NetconfMessage) msg;
190
191             // Ok response to start-exi, try to add exi handlers
192             if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
193                 LOG.trace("Positive response on start-exi call received on session {}", session);
194                 try {
195                     session.startExiCommunication(startExiMessage);
196                 } catch (RuntimeException e) {
197                     // Unable to add exi, continue without exi
198                     LOG.warn("Unable to start exi communication, Communication will continue without exi on session "
199                             + "{}", session, e);
200                 }
201
202                 // Error response
203             } else if (NetconfMessageUtil.isErrorMessage(netconfMessage)) {
204                 LOG.warn(
205                         "Error response to start-exi message {}, Communication will continue without exi on session {}",
206                         netconfMessage, session);
207
208                 // Unexpected response to start-exi, throwing message away, continue without exi
209             } else {
210                 LOG.warn("Unexpected response to start-exi message, should be ok, was {}, "
211                         + "Communication will continue without exi "
212                         + "and response message will be thrown away on session {}",
213                         netconfMessage, session);
214             }
215
216             negotiationSuccessful(session);
217         }
218     }
219
220 }