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