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