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