blob: e805ddae5b935b72c371e06cb665c1736fa6f6cc [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
53
andreaeb70a942015-10-16 21:34:46 -070054 private static final int CONNECTION_TIMEOUT = 0;
Andrea Campanella101417d2015-12-11 17:58:07 -080055 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080056 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080057 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080058 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080059 private static final String END_OF_RPC_OPEN_TAG = "\">";
60 private static final String EQUAL = "=";
61 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090062 private static final String RPC_OPEN = "<rpc ";
63 private static final String RPC_CLOSE = "</rpc>";
64 private static final String GET_OPEN = "<get>";
65 private static final String GET_CLOSE = "</get>";
66 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
67 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090068 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
69 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090070 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
71 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090072 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
73 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
74 private static final String TARGET_OPEN = "<target>";
75 private static final String TARGET_CLOSE = "</target>";
76 private static final String CONFIG_OPEN = "<config>";
77 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080078 private static final String XML_HEADER =
79 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090080 private static final String NETCONF_BASE_NAMESPACE =
81 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
82 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
83 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090084 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
85 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -070086
Andrea Campanellab029b9e2016-01-29 11:05:36 -080087 private final AtomicInteger messageIdInteger = new AtomicInteger(0);
andreaeb70a942015-10-16 21:34:46 -070088 private Connection netconfConnection;
89 private NetconfDeviceInfo deviceInfo;
90 private Session sshSession;
91 private boolean connectionActive;
andreaeb70a942015-10-16 21:34:46 -070092 private List<String> deviceCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -080093 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
andreaeb70a942015-10-16 21:34:46 -070094 private String serverCapabilities;
helenyrwu0407c642016-06-09 12:01:30 -070095 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -080096 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020097 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -070098 private boolean subscriptionConnected = false;
andreaeb70a942015-10-16 21:34:46 -070099
100
Andrea Campanella101417d2015-12-11 17:58:07 -0800101 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700102 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900103 this.netconfConnection = null;
104 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700105 connectionActive = false;
Andrea Campanella101417d2015-12-11 17:58:07 -0800106 replies = new HashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200107 errorReplies = new ArrayList<>();
andreaeb70a942015-10-16 21:34:46 -0700108 startConnection();
109 }
110
Andrea Campanella101417d2015-12-11 17:58:07 -0800111 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700112 if (!connectionActive) {
113 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Andrea Campanella101417d2015-12-11 17:58:07 -0800114 try {
115 netconfConnection.connect(null, CONNECTION_TIMEOUT, 5000);
116 } 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 {
121 if (deviceInfo.getKeyFile() != null) {
122 isAuthenticated = netconfConnection.authenticateWithPublicKey(
123 deviceInfo.name(), deviceInfo.getKeyFile(),
124 deviceInfo.password());
125 } else {
Andrea Campanella101417d2015-12-11 17:58:07 -0800126 log.debug("Authenticating to device {} with username {}",
Andrea Campanella50d25212016-02-26 13:06:23 -0800127 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700128 isAuthenticated = netconfConnection.authenticateWithPassword(
129 deviceInfo.name(), deviceInfo.password());
130 }
131 } catch (IOException e) {
Andrea Campanellad264b492016-03-01 09:46:06 -0800132 log.error("Authentication connection to device {} failed: {} ",
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900133 deviceInfo.getDeviceId(), e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800134 throw new NetconfException("Authentication connection to device " +
135 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700136 }
137
138 connectionActive = true;
139 Preconditions.checkArgument(isAuthenticated,
Andrea Campanellad264b492016-03-01 09:46:06 -0800140 "Authentication to device %s with username " +
141 "%s failed",
Andrea Campanella50d25212016-02-26 13:06:23 -0800142 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700143 startSshSession();
144 }
145 }
146
Andrea Campanella101417d2015-12-11 17:58:07 -0800147 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700148 try {
149 sshSession = netconfConnection.openSession();
150 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700151 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
152 sshSession.getStderr(), deviceInfo,
153 new NetconfSessionDelegateImpl());
Andrea Campanella101417d2015-12-11 17:58:07 -0800154 this.addDeviceOutputListener(new NetconfDeviceOutputEventListenerImpl(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700155 sendHello();
156 } catch (IOException e) {
Andrea Campanella1311ea02016-03-04 17:51:25 -0800157 log.error("Failed to create ch.ethz.ssh2.Session session." + e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800158 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
159 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700160 }
161 }
162
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900163
164 @Beta
165 private void startSubscriptionConnection(String filterSchema) throws NetconfException {
helenyrwu0407c642016-06-09 12:01:30 -0700166 if (!serverCapabilities.contains("interleave")) {
167 throw new NetconfException("Device" + deviceInfo + "does not support interleave");
168 }
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900169 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700170 if (!checkReply(reply)) {
171 throw new NetconfException("Subscription not successful with device "
172 + deviceInfo + " with reply " + reply);
173 }
174 subscriptionConnected = true;
175 }
176
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900177 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700178 public void startSubscription() throws NetconfException {
179 if (!subscriptionConnected) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900180 startSubscriptionConnection(null);
helenyrwu0407c642016-06-09 12:01:30 -0700181 }
182 streamHandler.setEnableNotifications(true);
183 }
184
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900185 @Beta
186 @Override
187 public void startSubscription(String filterSchema) throws NetconfException {
188 if (!subscriptionConnected) {
189 startSubscriptionConnection(filterSchema);
190 }
191 streamHandler.setEnableNotifications(true);
192 }
193
194 @Beta
195 private String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700196 StringBuilder subscriptionbuffer = new StringBuilder();
197 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
198 subscriptionbuffer.append(" <create-subscription\n");
199 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900200 // FIXME Only subtree filtering supported at the moment.
201 if (filterSchema != null) {
202 subscriptionbuffer.append(" ");
203 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
204 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
205 subscriptionbuffer.append(" ");
206 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
207 }
helenyrwu0407c642016-06-09 12:01:30 -0700208 subscriptionbuffer.append(" </create-subscription>\n");
209 subscriptionbuffer.append("</rpc>\n");
210 subscriptionbuffer.append(ENDPATTERN);
211 return subscriptionbuffer.toString();
212 }
213
214 @Override
215 public void endSubscription() throws NetconfException {
216 if (subscriptionConnected) {
217 streamHandler.setEnableNotifications(false);
218 } else {
219 throw new NetconfException("Subscription does not exist.");
220 }
221 }
222
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800223 private void sendHello() throws NetconfException {
Andrea Campanella101417d2015-12-11 17:58:07 -0800224 serverCapabilities = sendRequest(createHelloString());
andreaeb70a942015-10-16 21:34:46 -0700225 }
226
227 private String createHelloString() {
228 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800229 hellobuffer.append(XML_HEADER);
230 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700231 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
232 hellobuffer.append(" <capabilities>\n");
233 deviceCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800234 cap -> hellobuffer.append(" <capability>")
235 .append(cap)
236 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700237 hellobuffer.append(" </capabilities>\n");
238 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800239 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700240 return hellobuffer.toString();
241
242 }
243
Andrea Campanella101417d2015-12-11 17:58:07 -0800244 private void checkAndRestablishSession() throws NetconfException {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800245 if (sshSession.getState() != 2) {
246 try {
247 startSshSession();
248 } catch (IOException e) {
Andrea Campanella101417d2015-12-11 17:58:07 -0800249 log.debug("The connection with {} had to be reopened", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800250 try {
251 startConnection();
252 } catch (IOException e2) {
Andrea Campanella50d25212016-02-26 13:06:23 -0800253 log.error("No connection {} for device", netconfConnection, e2);
Andrea Campanella101417d2015-12-11 17:58:07 -0800254 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800255 }
256 }
257 }
258 }
259
andreaeb70a942015-10-16 21:34:46 -0700260 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800261 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800262 if (!request.contains(ENDPATTERN)) {
263 request = request + NEW_LINE + ENDPATTERN;
264 }
265 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300266 checkReply(reply);
267 return reply;
andreaeb70a942015-10-16 21:34:46 -0700268 }
269
270 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800271 public CompletableFuture<String> request(String request) {
helenyrwu0407c642016-06-09 12:01:30 -0700272 CompletableFuture<String> ftrep = streamHandler.sendMessage(request);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800273 replies.put(messageIdInteger.get(), ftrep);
Andrea Campanella101417d2015-12-11 17:58:07 -0800274 return ftrep;
275 }
276
277 private String sendRequest(String request) throws NetconfException {
278 checkAndRestablishSession();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800279 request = formatRequestMessageId(request);
280 request = formatXmlHeader(request);
Andrea Campanella101417d2015-12-11 17:58:07 -0800281 CompletableFuture<String> futureReply = request(request);
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200282 messageIdInteger.incrementAndGet();
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300283 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800284 String rp;
285 try {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300286 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800287 } catch (InterruptedException | ExecutionException | TimeoutException e) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200288 throw new NetconfException("No matching reply for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800289 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800290 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800291 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800292 }
293
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800294 private String formatRequestMessageId(String request) {
295 if (request.contains(MESSAGE_ID_STRING)) {
296 //FIXME if application provieds his own counting of messages this fails that count
297 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
298 MESSAGE_ID_STRING + EQUAL + "\"" + messageIdInteger.get() + "\"");
299 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
300 //FIXME find out a better way to enforce the presence of message-id
301 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
302 + messageIdInteger.get() + "\"" + ">");
303 }
304 return request;
305 }
306
307 private String formatXmlHeader(String request) {
308 if (!request.contains(XML_HEADER)) {
309 //FIXME if application provieds his own XML header of different type there is a clash
310 request = XML_HEADER + "\n" + request;
311 }
312 return request;
313 }
314
Andrea Campanella101417d2015-12-11 17:58:07 -0800315 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900316 public String doWrappedRpc(String request) throws NetconfException {
317 StringBuilder rpc = new StringBuilder(XML_HEADER);
318 rpc.append(RPC_OPEN);
319 rpc.append(MESSAGE_ID_STRING);
320 rpc.append(EQUAL);
321 rpc.append("\"");
322 rpc.append(messageIdInteger.get());
323 rpc.append("\" ");
324 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
325 rpc.append(request);
326 rpc.append(RPC_CLOSE).append(NEW_LINE);
327 rpc.append(ENDPATTERN);
328 String reply = sendRequest(rpc.toString());
329 checkReply(reply);
330 return reply;
331 }
332
333 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800334 public String get(String request) throws NetconfException {
335 return requestSync(request);
336 }
337
338 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900339 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
340 StringBuilder rpc = new StringBuilder(XML_HEADER);
341 rpc.append(RPC_OPEN);
342 rpc.append(MESSAGE_ID_STRING);
343 rpc.append(EQUAL);
344 rpc.append("\"");
345 rpc.append(messageIdInteger.get());
346 rpc.append("\" ");
347 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
348 rpc.append(GET_OPEN).append(NEW_LINE);
349 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900350 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900351 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900352 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900353 }
354 if (withDefaultsMode != null) {
355 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
356 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
357 }
358 rpc.append(GET_CLOSE).append(NEW_LINE);
359 rpc.append(RPC_CLOSE).append(NEW_LINE);
360 rpc.append(ENDPATTERN);
361 String reply = sendRequest(rpc.toString());
362 checkReply(reply);
363 return reply;
364 }
365
366 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800367 public String getConfig(String targetConfiguration) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700368 return getConfig(targetConfiguration, null);
369 }
370
371 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800372 public String getConfig(String targetConfiguration, String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800373 StringBuilder rpc = new StringBuilder(XML_HEADER);
374 rpc.append("<rpc ");
375 rpc.append(MESSAGE_ID_STRING);
376 rpc.append(EQUAL);
377 rpc.append("\"");
378 rpc.append(messageIdInteger.get());
379 rpc.append("\" ");
380 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700381 rpc.append("<get-config>\n");
382 rpc.append("<source>\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800383 rpc.append("<").append(targetConfiguration).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700384 rpc.append("</source>");
385 if (configurationSchema != null) {
386 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800387 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700388 rpc.append("</filter>\n");
389 }
390 rpc.append("</get-config>\n");
391 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800392 rpc.append(ENDPATTERN);
393 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800394 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700395 }
396
397 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800398 public boolean editConfig(String newConfiguration) throws NetconfException {
399 newConfiguration = newConfiguration + ENDPATTERN;
400 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700401 }
402
403 @Override
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800404 public boolean editConfig(String targetConfiguration, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800405 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800406 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800407 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900408 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800409 rpc.append(MESSAGE_ID_STRING);
410 rpc.append(EQUAL);
411 rpc.append("\"");
412 rpc.append(messageIdInteger.get());
413 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900414 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
415 rpc.append(EDIT_CONFIG_OPEN).append("\n");
416 rpc.append(TARGET_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800417 rpc.append("<").append(targetConfiguration).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900418 rpc.append(TARGET_CLOSE).append("\n");
419 if (mode != null) {
420 rpc.append(DEFAULT_OPERATION_OPEN);
421 rpc.append(mode);
422 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
423 }
424 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800425 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900426 rpc.append(CONFIG_CLOSE).append("\n");
427 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
428 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800429 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500430 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900431 String reply = sendRequest(rpc.toString());
432 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800433 }
434
435 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800436 public boolean copyConfig(String targetConfiguration, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800437 throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700438 newConfiguration = newConfiguration.trim();
yoonseona1957552016-11-02 15:53:07 -0400439 if (!newConfiguration.startsWith("<config>")) {
440 newConfiguration = "<config>" + newConfiguration
441 + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700442 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800443 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700444 rpc.append("<rpc>");
445 rpc.append("<copy-config>");
446 rpc.append("<target>");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800447 rpc.append("<").append(targetConfiguration).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700448 rpc.append("</target>");
449 rpc.append("<source>");
yoonseon25e54fc2016-11-02 13:07:43 -0400450 rpc.append(newConfiguration);
andreaeb70a942015-10-16 21:34:46 -0700451 rpc.append("</source>");
452 rpc.append("</copy-config>");
453 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800454 rpc.append(ENDPATTERN);
455 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700456 }
457
458 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800459 public boolean deleteConfig(String targetConfiguration) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700460 if (targetConfiguration.equals("running")) {
461 log.warn("Target configuration for delete operation can't be \"running\"",
462 targetConfiguration);
463 return false;
464 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800465 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700466 rpc.append("<rpc>");
467 rpc.append("<delete-config>");
468 rpc.append("<target>");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800469 rpc.append("<").append(targetConfiguration).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700470 rpc.append("</target>");
471 rpc.append("</delete-config>");
472 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800473 rpc.append(ENDPATTERN);
474 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700475 }
476
477 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700478 public boolean lock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800479 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700480 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700481 rpc.append("<lock>");
482 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700483 rpc.append("<");
484 rpc.append(configType);
485 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700486 rpc.append("</target>");
487 rpc.append("</lock>");
488 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800489 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700490 String lockReply = sendRequest(rpc.toString());
491 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700492 }
493
494 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700495 public boolean unlock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800496 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700497 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700498 rpc.append("<unlock>");
499 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700500 rpc.append("<");
501 rpc.append(configType);
502 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700503 rpc.append("</target>");
504 rpc.append("</unlock>");
505 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800506 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700507 String unlockReply = sendRequest(rpc.toString());
508 return checkReply(unlockReply);
509 }
510
511 @Override
512 public boolean lock() throws NetconfException {
513 return lock("running");
514 }
515
516 @Override
517 public boolean unlock() throws NetconfException {
518 return unlock("running");
andreaeb70a942015-10-16 21:34:46 -0700519 }
520
521 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800522 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700523 return close(false);
524 }
525
Andrea Campanella101417d2015-12-11 17:58:07 -0800526 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700527 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700528 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700529 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700530 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700531 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700532 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700533 }
andreaeb70a942015-10-16 21:34:46 -0700534 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800535 rpc.append(ENDPATTERN);
536 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700537 }
538
539 @Override
540 public String getSessionId() {
541 if (serverCapabilities.contains("<session-id>")) {
542 String[] outer = serverCapabilities.split("<session-id>");
543 Preconditions.checkArgument(outer.length != 1,
544 "Error in retrieving the session id");
545 String[] value = outer[1].split("</session-id>");
546 Preconditions.checkArgument(value.length != 1,
547 "Error in retrieving the session id");
548 return value[0];
549 } else {
550 return String.valueOf(-1);
551 }
552 }
553
554 @Override
555 public String getServerCapabilities() {
556 return serverCapabilities;
557 }
558
559 @Override
560 public void setDeviceCapabilities(List<String> capabilities) {
561 deviceCapabilities = capabilities;
562 }
563
Andrea Campanella101417d2015-12-11 17:58:07 -0800564 @Override
565 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700566 streamHandler.addDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800567 }
568
569 @Override
570 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700571 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800572 }
573
574 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700575 if (reply != null) {
576 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900577 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700578 return true;
579 } else if (reply.contains("<ok/>")
580 || (reply.contains("<rpc-error>")
581 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900582 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700583 return true;
584 }
585 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800586 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700587 return false;
588 }
589
Andrea Campanella101417d2015-12-11 17:58:07 -0800590 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700591
Andrea Campanella101417d2015-12-11 17:58:07 -0800592 @Override
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200593 public void notify(NetconfDeviceOutputEvent event) {
594 Optional<Integer> messageId = event.getMessageID();
helenyrwu0407c642016-06-09 12:01:30 -0700595
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200596 if (!messageId.isPresent()) {
597 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800598 log.error("Device {} sent error reply {}",
599 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200600 return;
601 }
602 CompletableFuture<String> completedReply =
603 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900604 if (completedReply != null) {
605 completedReply.complete(event.getMessagePayload());
606 }
andreaeb70a942015-10-16 21:34:46 -0700607 }
608 }
andreaeb70a942015-10-16 21:34:46 -0700609}