blob: 26ded2f1b651501a00a3b06a458453dcf4eeb198 [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
andreaeb70a942015-10-16 21:34:46 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.netconf.ctl;
18
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090019import com.google.common.annotations.Beta;
andreaeb70a942015-10-16 21:34:46 -070020import ch.ethz.ssh2.Connection;
21import ch.ethz.ssh2.Session;
22import com.google.common.base.Preconditions;
23import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080024import org.onosproject.netconf.NetconfDeviceOutputEvent;
25import org.onosproject.netconf.NetconfDeviceOutputEventListener;
26import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070027import org.onosproject.netconf.NetconfSession;
28import org.slf4j.Logger;
29import org.slf4j.LoggerFactory;
30
andreaeb70a942015-10-16 21:34:46 -070031import java.io.IOException;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020032import java.util.ArrayList;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080033import java.util.Collections;
Andrea Campanella101417d2015-12-11 17:58:07 -080034import java.util.HashMap;
andreaeb70a942015-10-16 21:34:46 -070035import java.util.List;
Andrea Campanella101417d2015-12-11 17:58:07 -080036import java.util.Map;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020037import java.util.Optional;
Andrea Campanella101417d2015-12-11 17:58:07 -080038import java.util.concurrent.CompletableFuture;
Andrea Campanellab029b9e2016-01-29 11:05:36 -080039import java.util.concurrent.ExecutionException;
40import java.util.concurrent.TimeUnit;
41import java.util.concurrent.TimeoutException;
Andrea Campanella101417d2015-12-11 17:58:07 -080042import java.util.concurrent.atomic.AtomicInteger;
43
andreaeb70a942015-10-16 21:34:46 -070044
45/**
46 * Implementation of a NETCONF session to talk to a device.
47 */
48public class NetconfSessionImpl implements NetconfSession {
49
Andrea Campanella101417d2015-12-11 17:58:07 -080050 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070051 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080052
Andrea Campanella101417d2015-12-11 17:58:07 -080053 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080054 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080055 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080056 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080057 private static final String END_OF_RPC_OPEN_TAG = "\">";
58 private static final String EQUAL = "=";
59 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090060 private static final String RPC_OPEN = "<rpc ";
61 private static final String RPC_CLOSE = "</rpc>";
62 private static final String GET_OPEN = "<get>";
63 private static final String GET_CLOSE = "</get>";
64 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
65 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090066 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
67 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090068 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
69 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090070 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
71 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
72 private static final String TARGET_OPEN = "<target>";
73 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000074 private static final String CONFIG_OPEN = "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090075 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080076 private static final String XML_HEADER =
77 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090078 private static final String NETCONF_BASE_NAMESPACE =
79 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
80 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
81 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090082 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
83 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -070084
Andrea Campanellab029b9e2016-01-29 11:05:36 -080085 private final AtomicInteger messageIdInteger = new AtomicInteger(0);
andreaeb70a942015-10-16 21:34:46 -070086 private Connection netconfConnection;
87 private NetconfDeviceInfo deviceInfo;
88 private Session sshSession;
89 private boolean connectionActive;
andreaeb70a942015-10-16 21:34:46 -070090 private List<String> deviceCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -080091 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
andreaeb70a942015-10-16 21:34:46 -070092 private String serverCapabilities;
helenyrwu0407c642016-06-09 12:01:30 -070093 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -080094 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020095 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -070096 private boolean subscriptionConnected = false;
andreaeb70a942015-10-16 21:34:46 -070097
98
Andrea Campanella101417d2015-12-11 17:58:07 -080099 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700100 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900101 this.netconfConnection = null;
102 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700103 connectionActive = false;
Andrea Campanella101417d2015-12-11 17:58:07 -0800104 replies = new HashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200105 errorReplies = new ArrayList<>();
andreaeb70a942015-10-16 21:34:46 -0700106 startConnection();
107 }
108
Andrea Campanella101417d2015-12-11 17:58:07 -0800109 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700110 if (!connectionActive) {
111 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000112 int connectTimeout = NetconfControllerImpl.netconfConnectTimeout;
113
Andrea Campanella101417d2015-12-11 17:58:07 -0800114 try {
Sean Condon334ad692016-12-13 17:56:56 +0000115 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800116 } catch (IOException e) {
117 throw new NetconfException("Cannot open a connection with device" + deviceInfo, e);
118 }
andreaeb70a942015-10-16 21:34:46 -0700119 boolean isAuthenticated;
120 try {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800121 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
122 log.debug("Authenticating with key file to device {} with username {}",
123 deviceInfo.getDeviceId(), deviceInfo.name());
124 isAuthenticated = netconfConnection.authenticateWithPublicKey(
125 deviceInfo.name(), deviceInfo.getKeyFile(),
126 deviceInfo.password().equals("") ? null : deviceInfo.password());
127 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600128 log.debug("Authenticating with key to device {} with username {}",
129 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700130 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600131 deviceInfo.name(), deviceInfo.getKey(),
132 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700133 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600134 log.debug("Authenticating to device {} with username {} with password",
Andrea Campanella50d25212016-02-26 13:06:23 -0800135 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700136 isAuthenticated = netconfConnection.authenticateWithPassword(
137 deviceInfo.name(), deviceInfo.password());
138 }
139 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500140 log.error("Authentication connection to device {} failed",
141 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800142 throw new NetconfException("Authentication connection to device " +
143 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700144 }
145
146 connectionActive = true;
147 Preconditions.checkArgument(isAuthenticated,
Andrea Campanellad264b492016-03-01 09:46:06 -0800148 "Authentication to device %s with username " +
149 "%s failed",
Andrea Campanella50d25212016-02-26 13:06:23 -0800150 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700151 startSshSession();
152 }
153 }
154
Andrea Campanella101417d2015-12-11 17:58:07 -0800155 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700156 try {
157 sshSession = netconfConnection.openSession();
158 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700159 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
160 sshSession.getStderr(), deviceInfo,
161 new NetconfSessionDelegateImpl());
Andrea Campanella101417d2015-12-11 17:58:07 -0800162 this.addDeviceOutputListener(new NetconfDeviceOutputEventListenerImpl(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700163 sendHello();
164 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500165 log.error("Failed to create ch.ethz.ssh2.Session session.", e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800166 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
167 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700168 }
169 }
170
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900171
172 @Beta
173 private void startSubscriptionConnection(String filterSchema) throws NetconfException {
helenyrwu0407c642016-06-09 12:01:30 -0700174 if (!serverCapabilities.contains("interleave")) {
175 throw new NetconfException("Device" + deviceInfo + "does not support interleave");
176 }
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900177 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700178 if (!checkReply(reply)) {
179 throw new NetconfException("Subscription not successful with device "
180 + deviceInfo + " with reply " + reply);
181 }
182 subscriptionConnected = true;
183 }
184
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900185 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700186 public void startSubscription() throws NetconfException {
187 if (!subscriptionConnected) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900188 startSubscriptionConnection(null);
helenyrwu0407c642016-06-09 12:01:30 -0700189 }
190 streamHandler.setEnableNotifications(true);
191 }
192
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900193 @Beta
194 @Override
195 public void startSubscription(String filterSchema) throws NetconfException {
196 if (!subscriptionConnected) {
197 startSubscriptionConnection(filterSchema);
198 }
199 streamHandler.setEnableNotifications(true);
200 }
201
202 @Beta
203 private String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700204 StringBuilder subscriptionbuffer = new StringBuilder();
205 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
206 subscriptionbuffer.append(" <create-subscription\n");
207 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900208 // FIXME Only subtree filtering supported at the moment.
209 if (filterSchema != null) {
210 subscriptionbuffer.append(" ");
211 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
212 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
213 subscriptionbuffer.append(" ");
214 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
215 }
helenyrwu0407c642016-06-09 12:01:30 -0700216 subscriptionbuffer.append(" </create-subscription>\n");
217 subscriptionbuffer.append("</rpc>\n");
218 subscriptionbuffer.append(ENDPATTERN);
219 return subscriptionbuffer.toString();
220 }
221
222 @Override
223 public void endSubscription() throws NetconfException {
224 if (subscriptionConnected) {
225 streamHandler.setEnableNotifications(false);
226 } else {
227 throw new NetconfException("Subscription does not exist.");
228 }
229 }
230
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800231 private void sendHello() throws NetconfException {
Andrea Campanella101417d2015-12-11 17:58:07 -0800232 serverCapabilities = sendRequest(createHelloString());
andreaeb70a942015-10-16 21:34:46 -0700233 }
234
235 private String createHelloString() {
236 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800237 hellobuffer.append(XML_HEADER);
238 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700239 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
240 hellobuffer.append(" <capabilities>\n");
241 deviceCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800242 cap -> hellobuffer.append(" <capability>")
243 .append(cap)
244 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700245 hellobuffer.append(" </capabilities>\n");
246 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800247 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700248 return hellobuffer.toString();
249
250 }
251
Andrea Campanella101417d2015-12-11 17:58:07 -0800252 private void checkAndRestablishSession() throws NetconfException {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800253 if (sshSession.getState() != 2) {
254 try {
255 startSshSession();
256 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500257 log.debug("The connection with {} was reopened", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800258 try {
259 startConnection();
260 } catch (IOException e2) {
Andrea Campanella50d25212016-02-26 13:06:23 -0800261 log.error("No connection {} for device", netconfConnection, e2);
Andrea Campanella101417d2015-12-11 17:58:07 -0800262 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800263 }
264 }
265 }
266 }
267
andreaeb70a942015-10-16 21:34:46 -0700268 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800269 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800270 if (!request.contains(ENDPATTERN)) {
271 request = request + NEW_LINE + ENDPATTERN;
272 }
273 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300274 checkReply(reply);
275 return reply;
andreaeb70a942015-10-16 21:34:46 -0700276 }
277
278 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800279 public CompletableFuture<String> request(String request) {
helenyrwu0407c642016-06-09 12:01:30 -0700280 CompletableFuture<String> ftrep = streamHandler.sendMessage(request);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800281 replies.put(messageIdInteger.get(), ftrep);
Andrea Campanella101417d2015-12-11 17:58:07 -0800282 return ftrep;
283 }
284
285 private String sendRequest(String request) throws NetconfException {
286 checkAndRestablishSession();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800287 request = formatRequestMessageId(request);
288 request = formatXmlHeader(request);
Andrea Campanella101417d2015-12-11 17:58:07 -0800289 CompletableFuture<String> futureReply = request(request);
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200290 messageIdInteger.incrementAndGet();
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300291 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800292 String rp;
293 try {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300294 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800295 } catch (InterruptedException | ExecutionException | TimeoutException e) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200296 throw new NetconfException("No matching reply for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800297 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800298 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800299 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800300 }
301
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800302 private String formatRequestMessageId(String request) {
303 if (request.contains(MESSAGE_ID_STRING)) {
304 //FIXME if application provieds his own counting of messages this fails that count
305 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
306 MESSAGE_ID_STRING + EQUAL + "\"" + messageIdInteger.get() + "\"");
307 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
308 //FIXME find out a better way to enforce the presence of message-id
309 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
310 + messageIdInteger.get() + "\"" + ">");
311 }
312 return request;
313 }
314
315 private String formatXmlHeader(String request) {
316 if (!request.contains(XML_HEADER)) {
317 //FIXME if application provieds his own XML header of different type there is a clash
318 request = XML_HEADER + "\n" + request;
319 }
320 return request;
321 }
322
Andrea Campanella101417d2015-12-11 17:58:07 -0800323 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900324 public String doWrappedRpc(String request) throws NetconfException {
325 StringBuilder rpc = new StringBuilder(XML_HEADER);
326 rpc.append(RPC_OPEN);
327 rpc.append(MESSAGE_ID_STRING);
328 rpc.append(EQUAL);
329 rpc.append("\"");
330 rpc.append(messageIdInteger.get());
331 rpc.append("\" ");
332 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
333 rpc.append(request);
334 rpc.append(RPC_CLOSE).append(NEW_LINE);
335 rpc.append(ENDPATTERN);
336 String reply = sendRequest(rpc.toString());
337 checkReply(reply);
338 return reply;
339 }
340
341 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800342 public String get(String request) throws NetconfException {
343 return requestSync(request);
344 }
345
346 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900347 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
348 StringBuilder rpc = new StringBuilder(XML_HEADER);
349 rpc.append(RPC_OPEN);
350 rpc.append(MESSAGE_ID_STRING);
351 rpc.append(EQUAL);
352 rpc.append("\"");
353 rpc.append(messageIdInteger.get());
354 rpc.append("\" ");
355 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
356 rpc.append(GET_OPEN).append(NEW_LINE);
357 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900358 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900359 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900360 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900361 }
362 if (withDefaultsMode != null) {
363 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
364 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
365 }
366 rpc.append(GET_CLOSE).append(NEW_LINE);
367 rpc.append(RPC_CLOSE).append(NEW_LINE);
368 rpc.append(ENDPATTERN);
369 String reply = sendRequest(rpc.toString());
370 checkReply(reply);
371 return reply;
372 }
373
374 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800375 public String getConfig(String targetConfiguration) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700376 return getConfig(targetConfiguration, null);
377 }
378
379 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800380 public String getConfig(String targetConfiguration, String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800381 StringBuilder rpc = new StringBuilder(XML_HEADER);
382 rpc.append("<rpc ");
383 rpc.append(MESSAGE_ID_STRING);
384 rpc.append(EQUAL);
385 rpc.append("\"");
386 rpc.append(messageIdInteger.get());
387 rpc.append("\" ");
388 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700389 rpc.append("<get-config>\n");
390 rpc.append("<source>\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800391 rpc.append("<").append(targetConfiguration).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700392 rpc.append("</source>");
393 if (configurationSchema != null) {
394 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800395 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700396 rpc.append("</filter>\n");
397 }
398 rpc.append("</get-config>\n");
399 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800400 rpc.append(ENDPATTERN);
401 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800402 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700403 }
404
405 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800406 public boolean editConfig(String newConfiguration) throws NetconfException {
407 newConfiguration = newConfiguration + ENDPATTERN;
408 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700409 }
410
411 @Override
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800412 public boolean editConfig(String targetConfiguration, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800413 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800414 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800415 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900416 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800417 rpc.append(MESSAGE_ID_STRING);
418 rpc.append(EQUAL);
419 rpc.append("\"");
420 rpc.append(messageIdInteger.get());
421 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900422 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
423 rpc.append(EDIT_CONFIG_OPEN).append("\n");
424 rpc.append(TARGET_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800425 rpc.append("<").append(targetConfiguration).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900426 rpc.append(TARGET_CLOSE).append("\n");
427 if (mode != null) {
428 rpc.append(DEFAULT_OPERATION_OPEN);
429 rpc.append(mode);
430 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
431 }
432 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800433 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900434 rpc.append(CONFIG_CLOSE).append("\n");
435 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
436 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800437 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500438 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900439 String reply = sendRequest(rpc.toString());
440 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800441 }
442
443 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800444 public boolean copyConfig(String targetConfiguration, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800445 throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700446 newConfiguration = newConfiguration.trim();
yoonseona1957552016-11-02 15:53:07 -0400447 if (!newConfiguration.startsWith("<config>")) {
448 newConfiguration = "<config>" + newConfiguration
449 + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700450 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800451 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400452 rpc.append(RPC_OPEN);
453 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700454 rpc.append("<copy-config>");
455 rpc.append("<target>");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800456 rpc.append("<").append(targetConfiguration).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700457 rpc.append("</target>");
458 rpc.append("<source>");
yoonseon25e54fc2016-11-02 13:07:43 -0400459 rpc.append(newConfiguration);
andreaeb70a942015-10-16 21:34:46 -0700460 rpc.append("</source>");
461 rpc.append("</copy-config>");
462 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800463 rpc.append(ENDPATTERN);
464 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700465 }
466
467 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800468 public boolean deleteConfig(String targetConfiguration) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700469 if (targetConfiguration.equals("running")) {
470 log.warn("Target configuration for delete operation can't be \"running\"",
471 targetConfiguration);
472 return false;
473 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800474 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700475 rpc.append("<rpc>");
476 rpc.append("<delete-config>");
477 rpc.append("<target>");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800478 rpc.append("<").append(targetConfiguration).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700479 rpc.append("</target>");
480 rpc.append("</delete-config>");
481 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800482 rpc.append(ENDPATTERN);
483 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700484 }
485
486 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700487 public boolean lock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800488 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700489 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700490 rpc.append("<lock>");
491 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700492 rpc.append("<");
493 rpc.append(configType);
494 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700495 rpc.append("</target>");
496 rpc.append("</lock>");
497 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800498 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700499 String lockReply = sendRequest(rpc.toString());
500 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700501 }
502
503 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700504 public boolean unlock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800505 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700506 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700507 rpc.append("<unlock>");
508 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700509 rpc.append("<");
510 rpc.append(configType);
511 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700512 rpc.append("</target>");
513 rpc.append("</unlock>");
514 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800515 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700516 String unlockReply = sendRequest(rpc.toString());
517 return checkReply(unlockReply);
518 }
519
520 @Override
521 public boolean lock() throws NetconfException {
522 return lock("running");
523 }
524
525 @Override
526 public boolean unlock() throws NetconfException {
527 return unlock("running");
andreaeb70a942015-10-16 21:34:46 -0700528 }
529
530 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800531 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700532 return close(false);
533 }
534
Andrea Campanella101417d2015-12-11 17:58:07 -0800535 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700536 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700537 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700538 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700539 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700540 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700541 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700542 }
andreaeb70a942015-10-16 21:34:46 -0700543 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800544 rpc.append(ENDPATTERN);
545 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700546 }
547
548 @Override
549 public String getSessionId() {
550 if (serverCapabilities.contains("<session-id>")) {
551 String[] outer = serverCapabilities.split("<session-id>");
552 Preconditions.checkArgument(outer.length != 1,
553 "Error in retrieving the session id");
554 String[] value = outer[1].split("</session-id>");
555 Preconditions.checkArgument(value.length != 1,
556 "Error in retrieving the session id");
557 return value[0];
558 } else {
559 return String.valueOf(-1);
560 }
561 }
562
563 @Override
564 public String getServerCapabilities() {
565 return serverCapabilities;
566 }
567
568 @Override
569 public void setDeviceCapabilities(List<String> capabilities) {
570 deviceCapabilities = capabilities;
571 }
572
Andrea Campanella101417d2015-12-11 17:58:07 -0800573 @Override
574 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700575 streamHandler.addDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800576 }
577
578 @Override
579 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700580 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800581 }
582
583 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700584 if (reply != null) {
585 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900586 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700587 return true;
588 } else if (reply.contains("<ok/>")
589 || (reply.contains("<rpc-error>")
590 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900591 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700592 return true;
593 }
594 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800595 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700596 return false;
597 }
598
Andrea Campanella101417d2015-12-11 17:58:07 -0800599 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700600
Andrea Campanella101417d2015-12-11 17:58:07 -0800601 @Override
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200602 public void notify(NetconfDeviceOutputEvent event) {
603 Optional<Integer> messageId = event.getMessageID();
helenyrwu0407c642016-06-09 12:01:30 -0700604
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200605 if (!messageId.isPresent()) {
606 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800607 log.error("Device {} sent error reply {}",
608 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200609 return;
610 }
611 CompletableFuture<String> completedReply =
612 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900613 if (completedReply != null) {
614 completedReply.complete(event.getMessagePayload());
615 }
andreaeb70a942015-10-16 21:34:46 -0700616 }
617 }
andreaeb70a942015-10-16 21:34:46 -0700618}