blob: 72ae51307ac13b2490e95e55b084a5829a6ec2bc [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;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +030023import org.onosproject.netconf.TargetConfig;
andreaeb70a942015-10-16 21:34:46 -070024import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080025import org.onosproject.netconf.NetconfDeviceOutputEvent;
26import org.onosproject.netconf.NetconfDeviceOutputEventListener;
27import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070028import org.onosproject.netconf.NetconfSession;
29import org.slf4j.Logger;
30import org.slf4j.LoggerFactory;
31
andreaeb70a942015-10-16 21:34:46 -070032import java.io.IOException;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020033import java.util.ArrayList;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080034import java.util.Collections;
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;
Sean Condond2c8d472017-02-17 17:09:39 +000039import java.util.concurrent.ConcurrentHashMap;
Andrea Campanellab029b9e2016-01-29 11:05:36 -080040import java.util.concurrent.ExecutionException;
41import java.util.concurrent.TimeUnit;
42import java.util.concurrent.TimeoutException;
Andrea Campanella101417d2015-12-11 17:58:07 -080043import java.util.concurrent.atomic.AtomicInteger;
Sean Condond2c8d472017-02-17 17:09:39 +000044import java.util.regex.Pattern;
45
Andrea Campanella101417d2015-12-11 17:58:07 -080046
andreaeb70a942015-10-16 21:34:46 -070047
48/**
49 * Implementation of a NETCONF session to talk to a device.
50 */
51public class NetconfSessionImpl implements NetconfSession {
52
Andrea Campanella101417d2015-12-11 17:58:07 -080053 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070054 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080055
Andrea Campanella101417d2015-12-11 17:58:07 -080056 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080057 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080058 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080059 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080060 private static final String END_OF_RPC_OPEN_TAG = "\">";
61 private static final String EQUAL = "=";
62 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090063 private static final String RPC_OPEN = "<rpc ";
64 private static final String RPC_CLOSE = "</rpc>";
65 private static final String GET_OPEN = "<get>";
66 private static final String GET_CLOSE = "</get>";
67 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
68 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090069 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
70 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090071 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
72 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090073 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
74 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
75 private static final String TARGET_OPEN = "<target>";
76 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000077 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 +090078 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080079 private static final String XML_HEADER =
80 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090081 private static final String NETCONF_BASE_NAMESPACE =
82 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
83 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
84 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090085 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
86 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -070087
Sean Condond2c8d472017-02-17 17:09:39 +000088 private static Pattern msgIdPattern = Pattern.compile("(message-id=\"[0-9]+\")");
89
Andrea Campanellab029b9e2016-01-29 11:05:36 -080090 private final AtomicInteger messageIdInteger = new AtomicInteger(0);
andreaeb70a942015-10-16 21:34:46 -070091 private Connection netconfConnection;
92 private NetconfDeviceInfo deviceInfo;
93 private Session sshSession;
94 private boolean connectionActive;
andreaeb70a942015-10-16 21:34:46 -070095 private List<String> deviceCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -080096 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
andreaeb70a942015-10-16 21:34:46 -070097 private String serverCapabilities;
helenyrwu0407c642016-06-09 12:01:30 -070098 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -080099 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200100 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -0700101 private boolean subscriptionConnected = false;
andreaeb70a942015-10-16 21:34:46 -0700102
103
Andrea Campanella101417d2015-12-11 17:58:07 -0800104 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700105 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900106 this.netconfConnection = null;
107 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700108 connectionActive = false;
Sean Condond2c8d472017-02-17 17:09:39 +0000109 replies = new ConcurrentHashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200110 errorReplies = new ArrayList<>();
andreaeb70a942015-10-16 21:34:46 -0700111 startConnection();
112 }
113
Andrea Campanella101417d2015-12-11 17:58:07 -0800114 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700115 if (!connectionActive) {
116 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000117 int connectTimeout = NetconfControllerImpl.netconfConnectTimeout;
118
Andrea Campanella101417d2015-12-11 17:58:07 -0800119 try {
Sean Condon334ad692016-12-13 17:56:56 +0000120 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800121 } catch (IOException e) {
Yuta HIGUCHI0184a7b2017-03-31 13:13:58 -0700122 throw new NetconfException("Cannot open a connection with device " + deviceInfo, e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800123 }
andreaeb70a942015-10-16 21:34:46 -0700124 boolean isAuthenticated;
125 try {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800126 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
127 log.debug("Authenticating with key file to device {} with username {}",
128 deviceInfo.getDeviceId(), deviceInfo.name());
129 isAuthenticated = netconfConnection.authenticateWithPublicKey(
130 deviceInfo.name(), deviceInfo.getKeyFile(),
131 deviceInfo.password().equals("") ? null : deviceInfo.password());
132 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600133 log.debug("Authenticating with key to device {} with username {}",
134 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700135 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600136 deviceInfo.name(), deviceInfo.getKey(),
137 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700138 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600139 log.debug("Authenticating to device {} with username {} with password",
Andrea Campanella50d25212016-02-26 13:06:23 -0800140 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700141 isAuthenticated = netconfConnection.authenticateWithPassword(
142 deviceInfo.name(), deviceInfo.password());
143 }
144 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500145 log.error("Authentication connection to device {} failed",
146 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800147 throw new NetconfException("Authentication connection to device " +
148 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700149 }
150
151 connectionActive = true;
152 Preconditions.checkArgument(isAuthenticated,
Andrea Campanellad264b492016-03-01 09:46:06 -0800153 "Authentication to device %s with username " +
154 "%s failed",
Andrea Campanella50d25212016-02-26 13:06:23 -0800155 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700156 startSshSession();
157 }
158 }
159
Andrea Campanella101417d2015-12-11 17:58:07 -0800160 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700161 try {
162 sshSession = netconfConnection.openSession();
163 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700164 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
165 sshSession.getStderr(), deviceInfo,
Sean Condond2c8d472017-02-17 17:09:39 +0000166 new NetconfSessionDelegateImpl(),
167 replies);
Andrea Campanella101417d2015-12-11 17:58:07 -0800168 this.addDeviceOutputListener(new NetconfDeviceOutputEventListenerImpl(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700169 sendHello();
170 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200171 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800172 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
173 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700174 }
175 }
176
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900177
178 @Beta
179 private void startSubscriptionConnection(String filterSchema) throws NetconfException {
helenyrwu0407c642016-06-09 12:01:30 -0700180 if (!serverCapabilities.contains("interleave")) {
181 throw new NetconfException("Device" + deviceInfo + "does not support interleave");
182 }
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900183 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700184 if (!checkReply(reply)) {
185 throw new NetconfException("Subscription not successful with device "
186 + deviceInfo + " with reply " + reply);
187 }
188 subscriptionConnected = true;
189 }
190
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900191 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700192 public void startSubscription() throws NetconfException {
193 if (!subscriptionConnected) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900194 startSubscriptionConnection(null);
helenyrwu0407c642016-06-09 12:01:30 -0700195 }
196 streamHandler.setEnableNotifications(true);
197 }
198
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900199 @Beta
200 @Override
201 public void startSubscription(String filterSchema) throws NetconfException {
202 if (!subscriptionConnected) {
203 startSubscriptionConnection(filterSchema);
204 }
205 streamHandler.setEnableNotifications(true);
206 }
207
208 @Beta
209 private String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700210 StringBuilder subscriptionbuffer = new StringBuilder();
211 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
212 subscriptionbuffer.append(" <create-subscription\n");
213 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900214 // FIXME Only subtree filtering supported at the moment.
215 if (filterSchema != null) {
216 subscriptionbuffer.append(" ");
217 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
218 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
219 subscriptionbuffer.append(" ");
220 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
221 }
helenyrwu0407c642016-06-09 12:01:30 -0700222 subscriptionbuffer.append(" </create-subscription>\n");
223 subscriptionbuffer.append("</rpc>\n");
224 subscriptionbuffer.append(ENDPATTERN);
225 return subscriptionbuffer.toString();
226 }
227
228 @Override
229 public void endSubscription() throws NetconfException {
230 if (subscriptionConnected) {
231 streamHandler.setEnableNotifications(false);
232 } else {
233 throw new NetconfException("Subscription does not exist.");
234 }
235 }
236
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800237 private void sendHello() throws NetconfException {
Andrea Campanella101417d2015-12-11 17:58:07 -0800238 serverCapabilities = sendRequest(createHelloString());
andreaeb70a942015-10-16 21:34:46 -0700239 }
240
241 private String createHelloString() {
242 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800243 hellobuffer.append(XML_HEADER);
244 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700245 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
246 hellobuffer.append(" <capabilities>\n");
247 deviceCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800248 cap -> hellobuffer.append(" <capability>")
249 .append(cap)
250 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700251 hellobuffer.append(" </capabilities>\n");
252 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800253 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700254 return hellobuffer.toString();
255
256 }
257
Andrea Campanella101417d2015-12-11 17:58:07 -0800258 private void checkAndRestablishSession() throws NetconfException {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800259 if (sshSession.getState() != 2) {
260 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200261 log.debug("The session with {} was reopened", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800262 startSshSession();
263 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500264 log.debug("The connection with {} was reopened", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800265 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200266 connectionActive = false;
267 replies.clear();
268 messageIdInteger.set(0);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800269 startConnection();
270 } catch (IOException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200271 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800272 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800273 }
274 }
275 }
276 }
277
andreaeb70a942015-10-16 21:34:46 -0700278 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800279 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800280 if (!request.contains(ENDPATTERN)) {
281 request = request + NEW_LINE + ENDPATTERN;
282 }
283 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300284 checkReply(reply);
285 return reply;
andreaeb70a942015-10-16 21:34:46 -0700286 }
287
288 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000289 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800290 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000291 return streamHandler.sendMessage(request);
292 }
293
294 private CompletableFuture<String> request(String request, int messageId) {
295 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800296 }
297
298 private String sendRequest(String request) throws NetconfException {
299 checkAndRestablishSession();
Sean Condond2c8d472017-02-17 17:09:39 +0000300 final int messageId = messageIdInteger.getAndIncrement();
301 request = formatRequestMessageId(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800302 request = formatXmlHeader(request);
Sean Condond2c8d472017-02-17 17:09:39 +0000303 CompletableFuture<String> futureReply = request(request, messageId);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300304 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800305 String rp;
306 try {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300307 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000308 replies.remove(messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800309 } catch (InterruptedException | ExecutionException | TimeoutException e) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200310 throw new NetconfException("No matching reply for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800311 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800312 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800313 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800314 }
315
Sean Condond2c8d472017-02-17 17:09:39 +0000316 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800317 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000318 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800319 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Sean Condond2c8d472017-02-17 17:09:39 +0000320 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800321 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
322 //FIXME find out a better way to enforce the presence of message-id
323 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000324 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800325 }
326 return request;
327 }
328
329 private String formatXmlHeader(String request) {
330 if (!request.contains(XML_HEADER)) {
331 //FIXME if application provieds his own XML header of different type there is a clash
332 request = XML_HEADER + "\n" + request;
333 }
334 return request;
335 }
336
Andrea Campanella101417d2015-12-11 17:58:07 -0800337 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900338 public String doWrappedRpc(String request) throws NetconfException {
339 StringBuilder rpc = new StringBuilder(XML_HEADER);
340 rpc.append(RPC_OPEN);
341 rpc.append(MESSAGE_ID_STRING);
342 rpc.append(EQUAL);
343 rpc.append("\"");
344 rpc.append(messageIdInteger.get());
345 rpc.append("\" ");
346 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
347 rpc.append(request);
348 rpc.append(RPC_CLOSE).append(NEW_LINE);
349 rpc.append(ENDPATTERN);
350 String reply = sendRequest(rpc.toString());
351 checkReply(reply);
352 return reply;
353 }
354
355 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800356 public String get(String request) throws NetconfException {
357 return requestSync(request);
358 }
359
360 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900361 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
362 StringBuilder rpc = new StringBuilder(XML_HEADER);
363 rpc.append(RPC_OPEN);
364 rpc.append(MESSAGE_ID_STRING);
365 rpc.append(EQUAL);
366 rpc.append("\"");
367 rpc.append(messageIdInteger.get());
368 rpc.append("\" ");
369 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
370 rpc.append(GET_OPEN).append(NEW_LINE);
371 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900372 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900373 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900374 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900375 }
376 if (withDefaultsMode != null) {
377 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
378 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
379 }
380 rpc.append(GET_CLOSE).append(NEW_LINE);
381 rpc.append(RPC_CLOSE).append(NEW_LINE);
382 rpc.append(ENDPATTERN);
383 String reply = sendRequest(rpc.toString());
384 checkReply(reply);
385 return reply;
386 }
387
388 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300389 public String getConfig(TargetConfig netconfTargetConfig) throws NetconfException {
390 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700391 }
392
393 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300394 public String getConfig(String netconfTargetConfig) throws NetconfException {
395 return getConfig(TargetConfig.valueOf(netconfTargetConfig));
396 }
397
398 @Override
399 public String getConfig(String netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
400 return getConfig(TargetConfig.valueOf(netconfTargetConfig), configurationFilterSchema);
401 }
402
403 @Override
404 public String getConfig(TargetConfig netconfTargetConfig, String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800405 StringBuilder rpc = new StringBuilder(XML_HEADER);
406 rpc.append("<rpc ");
407 rpc.append(MESSAGE_ID_STRING);
408 rpc.append(EQUAL);
409 rpc.append("\"");
410 rpc.append(messageIdInteger.get());
411 rpc.append("\" ");
412 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700413 rpc.append("<get-config>\n");
414 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300415 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700416 rpc.append("</source>");
417 if (configurationSchema != null) {
418 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800419 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700420 rpc.append("</filter>\n");
421 }
422 rpc.append("</get-config>\n");
423 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800424 rpc.append(ENDPATTERN);
425 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800426 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700427 }
428
429 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800430 public boolean editConfig(String newConfiguration) throws NetconfException {
431 newConfiguration = newConfiguration + ENDPATTERN;
432 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700433 }
434
435 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300436 public boolean editConfig(String netconfTargetConfig, String mode, String newConfiguration)
437 throws NetconfException {
438 return editConfig(TargetConfig.valueOf(netconfTargetConfig), mode, newConfiguration);
439 }
440
441 @Override
442 public boolean editConfig(TargetConfig netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800443 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800444 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800445 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900446 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800447 rpc.append(MESSAGE_ID_STRING);
448 rpc.append(EQUAL);
449 rpc.append("\"");
450 rpc.append(messageIdInteger.get());
451 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900452 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
453 rpc.append(EDIT_CONFIG_OPEN).append("\n");
454 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300455 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900456 rpc.append(TARGET_CLOSE).append("\n");
457 if (mode != null) {
458 rpc.append(DEFAULT_OPERATION_OPEN);
459 rpc.append(mode);
460 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
461 }
462 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800463 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900464 rpc.append(CONFIG_CLOSE).append("\n");
465 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
466 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800467 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500468 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900469 String reply = sendRequest(rpc.toString());
470 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800471 }
472
473 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300474 public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
475 return copyConfig(TargetConfig.valueOf(netconfTargetConfig), newConfiguration);
476 }
477
478 @Override
479 public boolean copyConfig(TargetConfig netconfTargetConfig, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800480 throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700481 newConfiguration = newConfiguration.trim();
yoonseona1957552016-11-02 15:53:07 -0400482 if (!newConfiguration.startsWith("<config>")) {
483 newConfiguration = "<config>" + newConfiguration
484 + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700485 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800486 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400487 rpc.append(RPC_OPEN);
488 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700489 rpc.append("<copy-config>");
490 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300491 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700492 rpc.append("</target>");
493 rpc.append("<source>");
yoonseon25e54fc2016-11-02 13:07:43 -0400494 rpc.append(newConfiguration);
andreaeb70a942015-10-16 21:34:46 -0700495 rpc.append("</source>");
496 rpc.append("</copy-config>");
497 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800498 rpc.append(ENDPATTERN);
499 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700500 }
501
502 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300503 public boolean deleteConfig(String netconfTargetConfig) throws NetconfException {
504 return deleteConfig(TargetConfig.valueOf(netconfTargetConfig));
505 }
506
507 @Override
508 public boolean deleteConfig(TargetConfig netconfTargetConfig) throws NetconfException {
509 if (netconfTargetConfig.equals("running")) {
andreaeb70a942015-10-16 21:34:46 -0700510 log.warn("Target configuration for delete operation can't be \"running\"",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300511 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700512 return false;
513 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800514 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700515 rpc.append("<rpc>");
516 rpc.append("<delete-config>");
517 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300518 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700519 rpc.append("</target>");
520 rpc.append("</delete-config>");
521 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800522 rpc.append(ENDPATTERN);
523 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700524 }
525
526 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700527 public boolean lock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800528 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700529 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700530 rpc.append("<lock>");
531 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700532 rpc.append("<");
533 rpc.append(configType);
534 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700535 rpc.append("</target>");
536 rpc.append("</lock>");
537 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800538 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700539 String lockReply = sendRequest(rpc.toString());
540 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700541 }
542
543 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700544 public boolean unlock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800545 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700546 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700547 rpc.append("<unlock>");
548 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700549 rpc.append("<");
550 rpc.append(configType);
551 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700552 rpc.append("</target>");
553 rpc.append("</unlock>");
554 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800555 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700556 String unlockReply = sendRequest(rpc.toString());
557 return checkReply(unlockReply);
558 }
559
560 @Override
561 public boolean lock() throws NetconfException {
562 return lock("running");
563 }
564
565 @Override
566 public boolean unlock() throws NetconfException {
567 return unlock("running");
andreaeb70a942015-10-16 21:34:46 -0700568 }
569
570 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800571 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700572 return close(false);
573 }
574
Andrea Campanella101417d2015-12-11 17:58:07 -0800575 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700576 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700577 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700578 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700579 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700580 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700581 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700582 }
andreaeb70a942015-10-16 21:34:46 -0700583 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800584 rpc.append(ENDPATTERN);
585 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700586 }
587
588 @Override
589 public String getSessionId() {
590 if (serverCapabilities.contains("<session-id>")) {
591 String[] outer = serverCapabilities.split("<session-id>");
592 Preconditions.checkArgument(outer.length != 1,
593 "Error in retrieving the session id");
594 String[] value = outer[1].split("</session-id>");
595 Preconditions.checkArgument(value.length != 1,
596 "Error in retrieving the session id");
597 return value[0];
598 } else {
599 return String.valueOf(-1);
600 }
601 }
602
603 @Override
604 public String getServerCapabilities() {
605 return serverCapabilities;
606 }
607
608 @Override
609 public void setDeviceCapabilities(List<String> capabilities) {
610 deviceCapabilities = capabilities;
611 }
612
Andrea Campanella101417d2015-12-11 17:58:07 -0800613 @Override
614 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700615 streamHandler.addDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800616 }
617
618 @Override
619 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700620 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800621 }
622
623 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700624 if (reply != null) {
625 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900626 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700627 return true;
628 } else if (reply.contains("<ok/>")
629 || (reply.contains("<rpc-error>")
630 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900631 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700632 return true;
633 }
634 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800635 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700636 return false;
637 }
638
Andrea Campanella101417d2015-12-11 17:58:07 -0800639 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700640
Andrea Campanella101417d2015-12-11 17:58:07 -0800641 @Override
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200642 public void notify(NetconfDeviceOutputEvent event) {
643 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200644 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
645 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200646 if (!messageId.isPresent()) {
647 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800648 log.error("Device {} sent error reply {}",
649 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200650 return;
651 }
652 CompletableFuture<String> completedReply =
653 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900654 if (completedReply != null) {
655 completedReply.complete(event.getMessagePayload());
656 }
andreaeb70a942015-10-16 21:34:46 -0700657 }
658 }
andreaeb70a942015-10-16 21:34:46 -0700659}