blob: cdbde456bd9510c5ed5358e2e1860c211870133e [file] [log] [blame]
Andrea Campanella7bbe7b12017-05-03 16:03:38 -07001/*
2 * Copyright 2015-present Open Networking Laboratory
3 *
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.impl;
18
19import com.google.common.annotations.Beta;
20import com.google.common.base.MoreObjects;
21import com.google.common.base.Objects;
22import com.google.common.collect.ImmutableSet;
23import org.apache.sshd.client.SshClient;
24import org.apache.sshd.client.channel.ClientChannel;
25import org.apache.sshd.client.future.ConnectFuture;
26import org.apache.sshd.client.future.OpenFuture;
27import org.apache.sshd.client.session.ClientSession;
28import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
29import org.onosproject.netconf.NetconfDeviceInfo;
30import org.onosproject.netconf.NetconfDeviceOutputEvent;
31import org.onosproject.netconf.NetconfDeviceOutputEvent.Type;
32import org.onosproject.netconf.NetconfDeviceOutputEventListener;
33import org.onosproject.netconf.NetconfException;
34import org.onosproject.netconf.NetconfSession;
35import org.onosproject.netconf.NetconfSessionFactory;
36import org.onosproject.netconf.TargetConfig;
37import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39import java.io.IOException;
40import java.nio.ByteBuffer;
41import java.nio.CharBuffer;
42import java.nio.charset.StandardCharsets;
43import java.security.KeyFactory;
44import java.security.KeyPair;
45import java.security.NoSuchAlgorithmException;
46import java.security.PublicKey;
47import java.security.spec.InvalidKeySpecException;
48import java.security.spec.X509EncodedKeySpec;
49import java.util.ArrayList;
50import java.util.Collection;
51import java.util.Collections;
52import java.util.LinkedHashSet;
53import java.util.List;
54import java.util.Map;
55import java.util.Optional;
56import java.util.Set;
57import java.util.concurrent.CompletableFuture;
58import java.util.concurrent.ConcurrentHashMap;
59import java.util.concurrent.CopyOnWriteArrayList;
60import java.util.concurrent.ExecutionException;
61import java.util.concurrent.TimeUnit;
62import java.util.concurrent.TimeoutException;
63import java.util.concurrent.atomic.AtomicInteger;
64import java.util.regex.Matcher;
65import java.util.regex.Pattern;
66
67/**
68 * Implementation of a NETCONF session to talk to a device.
69 */
70public class NetconfSessionMinaImpl implements NetconfSession {
71
72 private static final Logger log = LoggerFactory
73 .getLogger(NetconfSessionMinaImpl.class);
74
75 private static final String ENDPATTERN = "]]>]]>";
76 private static final String MESSAGE_ID_STRING = "message-id";
77 private static final String HELLO = "<hello";
78 private static final String NEW_LINE = "\n";
79 private static final String END_OF_RPC_OPEN_TAG = "\">";
80 private static final String EQUAL = "=";
81 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
82 private static final String RPC_OPEN = "<rpc ";
83 private static final String RPC_CLOSE = "</rpc>";
84 private static final String GET_OPEN = "<get>";
85 private static final String GET_CLOSE = "</get>";
86 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
87 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
88 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
89 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
90 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
91 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
92 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
93 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
94 private static final String TARGET_OPEN = "<target>";
95 private static final String TARGET_CLOSE = "</target>";
96 private static final String CONFIG_OPEN = "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
97 private static final String CONFIG_CLOSE = "</config>";
98 private static final String XML_HEADER =
99 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
100 private static final String NETCONF_BASE_NAMESPACE =
101 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
102 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
103 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
104 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
105 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
106
107 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
108
109 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
110 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
111
112 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
113 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
114 private static final String RSA = "RSA";
115 private static final String DSA = "DSA";
116
117 private String sessionID;
118 private final AtomicInteger messageIdInteger = new AtomicInteger(1);
119 protected final NetconfDeviceInfo deviceInfo;
120 private Iterable<String> onosCapabilities =
121 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
122
123 /* NOTE: the "serverHelloResponseOld" is deprecated in 1.10.0 and should eventually be removed */
124 @Deprecated
125 private String serverHelloResponseOld;
126 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
127 private NetconfStreamHandler streamHandler;
128 private Map<Integer, CompletableFuture<String>> replies;
129 private List<String> errorReplies;
130 private boolean subscriptionConnected = false;
131 private String notificationFilterSchema = null;
132
133 private final Collection<NetconfDeviceOutputEventListener> primaryListeners =
134 new CopyOnWriteArrayList<>();
135 private final Collection<NetconfSession> children =
136 new CopyOnWriteArrayList<>();
137
138
139 private ClientChannel channel = null;
140 private ClientSession session = null;
141 private SshClient client = null;
142
143
144 public NetconfSessionMinaImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
145 this.deviceInfo = deviceInfo;
146 replies = new ConcurrentHashMap<>();
147 errorReplies = new ArrayList<>();
148 startConnection();
149 }
150
151 private void startConnection() throws NetconfException {
152 try {
153 startClient();
154 } catch (IOException e) {
155 throw new NetconfException("Failed to establish SSH with device " + deviceInfo, e);
156 }
157 }
158
159 private void startClient() throws IOException {
160 client = SshClient.setUpDefaultClient();
161 client.start();
162 client.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
163 startSession();
164 }
165
166 private void startSession() throws IOException {
167 final ConnectFuture connectFuture;
168 connectFuture = client.connect(deviceInfo.name(),
169 deviceInfo.ip().toString(),
170 deviceInfo.port())
171 .verify(NetconfControllerImpl.netconfConnectTimeout, TimeUnit.SECONDS);
172 session = connectFuture.getSession();
173 //Using the device ssh key if possible
174 if (deviceInfo.getKey() != null) {
175 ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(deviceInfo.getKey()));
176 byte[] byteKey = new byte[buf.limit()];
177 buf.get(byteKey);
178 PublicKey key;
179 try {
180 key = getPublicKey(byteKey, RSA);
181 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
182 try {
183 key = getPublicKey(byteKey, DSA);
184 } catch (NoSuchAlgorithmException | InvalidKeySpecException e1) {
185 throw new NetconfException("Failed to authenticate session with device " +
186 deviceInfo + "check key to be the " +
187 "proper DSA or RSA key", e1);
188 }
189 }
190 //privateKye can set tu null because is not used by the method.
191 session.addPublicKeyIdentity(new KeyPair(key, null));
192 } else {
193 session.addPasswordIdentity(deviceInfo.password());
194 }
195 session.auth().verify(NetconfControllerImpl.netconfConnectTimeout, TimeUnit.SECONDS);
196 Set<ClientSession.ClientSessionEvent> event = session.waitFor(
197 ImmutableSet.of(ClientSession.ClientSessionEvent.WAIT_AUTH,
198 ClientSession.ClientSessionEvent.CLOSED,
199 ClientSession.ClientSessionEvent.AUTHED), 0);
200
201 if (!event.contains(ClientSession.ClientSessionEvent.AUTHED)) {
202 log.debug("Session closed {} {}", event, session.isClosed());
203 throw new NetconfException("Failed to authenticate session with device " +
204 deviceInfo + "check the user/pwd or key");
205 }
206 openChannel();
207 }
208
209 private PublicKey getPublicKey(byte[] keyBytes, String type)
210 throws NoSuchAlgorithmException, InvalidKeySpecException {
211
212 X509EncodedKeySpec spec =
213 new X509EncodedKeySpec(keyBytes);
214 KeyFactory kf = KeyFactory.getInstance(type);
215 return kf.generatePublic(spec);
216 }
217
218 private void openChannel() throws IOException {
219 channel = session.createSubsystemChannel("netconf");
220 OpenFuture channelFuture = channel.open();
221 if (channelFuture.await(NetconfControllerImpl.netconfConnectTimeout, TimeUnit.SECONDS)) {
222 if (channelFuture.isOpened()) {
223 streamHandler = new NetconfStreamThread(channel.getInvertedOut(), channel.getInvertedIn(),
224 channel.getInvertedErr(), deviceInfo,
225 new NetconfSessionDelegateImpl(), replies);
226 } else {
227 throw new NetconfException("Failed to open channel with device " +
228 deviceInfo);
229 }
230 sendHello();
231 }
232 }
233
234
235 @Beta
236 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
237 boolean openNewSession = false;
238 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
239 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
240 openNewSession = true;
241
242 } else if (subscriptionConnected &&
243 notificationFilterSchema != null &&
244 !Objects.equal(filterSchema, notificationFilterSchema)) {
245 // interleave supported and existing filter is NOT "no filtering"
246 // and was requested with different filtering schema
247 log.info("Cannot use existing session for subscription {} ({})",
248 deviceInfo, filterSchema);
249 openNewSession = true;
250 }
251
252 if (openNewSession) {
253 log.info("Creating notification session to {} with filter {}",
254 deviceInfo, filterSchema);
255 NetconfSession child = new NotificationSession(deviceInfo);
256
257 child.addDeviceOutputListener(new NotificationForwarder());
258
259 child.startSubscription(filterSchema);
260 children.add(child);
261 return;
262 }
263
264 // request to start interleaved notification session
265 String reply = sendRequest(createSubscriptionString(filterSchema));
266 if (!checkReply(reply)) {
267 throw new NetconfException("Subscription not successful with device "
268 + deviceInfo + " with reply " + reply);
269 }
270 subscriptionConnected = true;
271 }
272
273 @Override
274 public void startSubscription() throws NetconfException {
275 if (!subscriptionConnected) {
276 startSubscriptionStream(null);
277 }
278 streamHandler.setEnableNotifications(true);
279 }
280
281 @Beta
282 @Override
283 public void startSubscription(String filterSchema) throws NetconfException {
284 if (!subscriptionConnected) {
285 notificationFilterSchema = filterSchema;
286 startSubscriptionStream(filterSchema);
287 }
288 streamHandler.setEnableNotifications(true);
289 }
290
291 @Beta
292 protected String createSubscriptionString(String filterSchema) {
293 StringBuilder subscriptionbuffer = new StringBuilder();
294 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
295 subscriptionbuffer.append(" <create-subscription\n");
296 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
297 // FIXME Only subtree filtering supported at the moment.
298 if (filterSchema != null) {
299 subscriptionbuffer.append(" ");
300 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
301 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
302 subscriptionbuffer.append(" ");
303 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
304 }
305 subscriptionbuffer.append(" </create-subscription>\n");
306 subscriptionbuffer.append("</rpc>\n");
307 subscriptionbuffer.append(ENDPATTERN);
308 return subscriptionbuffer.toString();
309 }
310
311 @Override
312 public void endSubscription() throws NetconfException {
313 if (subscriptionConnected) {
314 streamHandler.setEnableNotifications(false);
315 } else {
316 throw new NetconfException("Subscription does not exist.");
317 }
318 }
319
320 private void sendHello() throws NetconfException {
321 serverHelloResponseOld = sendRequest(createHelloString(), true);
322 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
323 while (capabilityMatcher.find()) {
324 deviceCapabilities.add(capabilityMatcher.group(1));
325 }
326 sessionID = String.valueOf(-1);
327 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
328 if (sessionIDMatcher.find()) {
329 sessionID = sessionIDMatcher.group(1);
330 } else {
331 throw new NetconfException("Missing SessionID in server hello " +
332 "reponse.");
333 }
334
335 }
336
337 private String createHelloString() {
338 StringBuilder hellobuffer = new StringBuilder();
339 hellobuffer.append(XML_HEADER);
340 hellobuffer.append("\n");
341 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
342 hellobuffer.append(" <capabilities>\n");
343 onosCapabilities.forEach(
344 cap -> hellobuffer.append(" <capability>")
345 .append(cap)
346 .append("</capability>\n"));
347 hellobuffer.append(" </capabilities>\n");
348 hellobuffer.append("</hello>\n");
349 hellobuffer.append(ENDPATTERN);
350 return hellobuffer.toString();
351
352 }
353
354 @Override
355 public void checkAndReestablish() throws NetconfException {
356 try {
357 if (client.isClosed()) {
358 log.debug("Trying to restart the whole SSH connection with {}", deviceInfo.getDeviceId());
359 cleanUp();
360 startConnection();
361 } else if (session.isClosed()) {
362 log.debug("Trying to restart the session with {}", session, deviceInfo.getDeviceId());
363 cleanUp();
364 startSession();
365 } else if (channel.isClosed()) {
366 log.debug("Trying to reopen the channel with {}", deviceInfo.getDeviceId());
367 cleanUp();
368 openChannel();
369 }
370 if (subscriptionConnected) {
371 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
372 subscriptionConnected = false;
373 startSubscription(notificationFilterSchema);
374 }
375 } catch (IOException e) {
376 log.error("Can't reopen connection for device {}", e.getMessage());
377 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
378 }
379 }
380
381 private void cleanUp() {
382 //makes sure everything is at a clean state.
383 replies.clear();
384 }
385
386 @Override
387 public String requestSync(String request) throws NetconfException {
388 if (!request.contains(ENDPATTERN)) {
389 request = request + NEW_LINE + ENDPATTERN;
390 }
391 String reply = sendRequest(request);
392 checkReply(reply);
393 return reply;
394 }
395
396 @Override
397 @Deprecated
398 public CompletableFuture<String> request(String request) {
399 return streamHandler.sendMessage(request);
400 }
401
402 private CompletableFuture<String> request(String request, int messageId) {
403 return streamHandler.sendMessage(request, messageId);
404 }
405
406 private String sendRequest(String request) throws NetconfException {
407 return sendRequest(request, false);
408 }
409
410 private String sendRequest(String request, boolean isHello) throws NetconfException {
411 checkAndReestablish();
412 int messageId = -1;
413 if (!isHello) {
414 messageId = messageIdInteger.getAndIncrement();
415 }
416 request = formatRequestMessageId(request, messageId);
417 request = formatXmlHeader(request);
418 CompletableFuture<String> futureReply = request(request, messageId);
419 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
420 String rp;
421 try {
422 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
423 replies.remove(messageId);
424 } catch (InterruptedException | ExecutionException | TimeoutException e) {
425 throw new NetconfException("No matching reply for request " + request, e);
426 }
427 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
428 return rp.trim();
429 }
430
431 private String formatRequestMessageId(String request, int messageId) {
432 if (request.contains(MESSAGE_ID_STRING)) {
433 //FIXME if application provides his own counting of messages this fails that count
434 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
435 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
436 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
437 //FIXME find out a better way to enforce the presence of message-id
438 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
439 + messageId + "\"" + ">");
440 }
441 return request;
442 }
443
444 private String formatXmlHeader(String request) {
445 if (!request.contains(XML_HEADER)) {
446 //FIXME if application provieds his own XML header of different type there is a clash
447 request = XML_HEADER + "\n" + request;
448 }
449 return request;
450 }
451
452 @Override
453 public String doWrappedRpc(String request) throws NetconfException {
454 StringBuilder rpc = new StringBuilder(XML_HEADER);
455 rpc.append(RPC_OPEN);
456 rpc.append(MESSAGE_ID_STRING);
457 rpc.append(EQUAL);
458 rpc.append("\"");
459 rpc.append(messageIdInteger.get());
460 rpc.append("\" ");
461 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
462 rpc.append(request);
463 rpc.append(RPC_CLOSE).append(NEW_LINE);
464 rpc.append(ENDPATTERN);
465 String reply = sendRequest(rpc.toString());
466 checkReply(reply);
467 return reply;
468 }
469
470 @Override
471 public String get(String request) throws NetconfException {
472 return requestSync(request);
473 }
474
475 @Override
476 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
477 StringBuilder rpc = new StringBuilder(XML_HEADER);
478 rpc.append(RPC_OPEN);
479 rpc.append(MESSAGE_ID_STRING);
480 rpc.append(EQUAL);
481 rpc.append("\"");
482 rpc.append(messageIdInteger.get());
483 rpc.append("\" ");
484 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
485 rpc.append(GET_OPEN).append(NEW_LINE);
486 if (filterSchema != null) {
487 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
488 rpc.append(filterSchema).append(NEW_LINE);
489 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
490 }
491 if (withDefaultsMode != null) {
492 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
493 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
494 }
495 rpc.append(GET_CLOSE).append(NEW_LINE);
496 rpc.append(RPC_CLOSE).append(NEW_LINE);
497 rpc.append(ENDPATTERN);
498 String reply = sendRequest(rpc.toString());
499 checkReply(reply);
500 return reply;
501 }
502
503 @Override
504 public String getConfig(TargetConfig netconfTargetConfig) throws NetconfException {
505 return getConfig(netconfTargetConfig, null);
506 }
507
508 @Override
509 public String getConfig(String netconfTargetConfig) throws NetconfException {
510 return getConfig(TargetConfig.toTargetConfig(netconfTargetConfig));
511 }
512
513 @Override
514 public String getConfig(String netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
515 return getConfig(TargetConfig.toTargetConfig(netconfTargetConfig), configurationFilterSchema);
516 }
517
518 @Override
519 public String getConfig(TargetConfig netconfTargetConfig, String configurationSchema) throws NetconfException {
520 StringBuilder rpc = new StringBuilder(XML_HEADER);
521 rpc.append("<rpc ");
522 rpc.append(MESSAGE_ID_STRING);
523 rpc.append(EQUAL);
524 rpc.append("\"");
525 rpc.append(messageIdInteger.get());
526 rpc.append("\" ");
527 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
528 rpc.append("<get-config>\n");
529 rpc.append("<source>\n");
530 rpc.append("<").append(netconfTargetConfig).append("/>");
531 rpc.append("</source>");
532 if (configurationSchema != null) {
533 rpc.append("<filter type=\"subtree\">\n");
534 rpc.append(configurationSchema).append("\n");
535 rpc.append("</filter>\n");
536 }
537 rpc.append("</get-config>\n");
538 rpc.append("</rpc>\n");
539 rpc.append(ENDPATTERN);
540 String reply = sendRequest(rpc.toString());
541 return checkReply(reply) ? reply : "ERROR " + reply;
542 }
543
544 @Override
545 public boolean editConfig(String newConfiguration) throws NetconfException {
546 newConfiguration = newConfiguration + ENDPATTERN;
547 return checkReply(sendRequest(newConfiguration));
548 }
549
550 @Override
551 public boolean editConfig(String netconfTargetConfig, String mode, String newConfiguration)
552 throws NetconfException {
553 return editConfig(TargetConfig.toTargetConfig(netconfTargetConfig), mode, newConfiguration);
554 }
555
556 @Override
557 public boolean editConfig(TargetConfig netconfTargetConfig, String mode, String newConfiguration)
558 throws NetconfException {
559 newConfiguration = newConfiguration.trim();
560 StringBuilder rpc = new StringBuilder(XML_HEADER);
561 rpc.append(RPC_OPEN);
562 rpc.append(MESSAGE_ID_STRING);
563 rpc.append(EQUAL);
564 rpc.append("\"");
565 rpc.append(messageIdInteger.get());
566 rpc.append("\" ");
567 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
568 rpc.append(EDIT_CONFIG_OPEN).append("\n");
569 rpc.append(TARGET_OPEN);
570 rpc.append("<").append(netconfTargetConfig).append("/>");
571 rpc.append(TARGET_CLOSE).append("\n");
572 if (mode != null) {
573 rpc.append(DEFAULT_OPERATION_OPEN);
574 rpc.append(mode);
575 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
576 }
577 rpc.append(CONFIG_OPEN).append("\n");
578 rpc.append(newConfiguration);
579 rpc.append(CONFIG_CLOSE).append("\n");
580 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
581 rpc.append(RPC_CLOSE);
582 rpc.append(ENDPATTERN);
583 log.debug(rpc.toString());
584 String reply = sendRequest(rpc.toString());
585 return checkReply(reply);
586 }
587
588 @Override
589 public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
590 return copyConfig(TargetConfig.toTargetConfig(netconfTargetConfig), newConfiguration);
591 }
592
593 @Override
594 public boolean copyConfig(TargetConfig netconfTargetConfig, String newConfiguration)
595 throws NetconfException {
596 newConfiguration = newConfiguration.trim();
597 if (!newConfiguration.startsWith("<config>")) {
598 newConfiguration = "<config>" + newConfiguration
599 + "</config>";
600 }
601 StringBuilder rpc = new StringBuilder(XML_HEADER);
602 rpc.append(RPC_OPEN);
603 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
604 rpc.append("<copy-config>");
605 rpc.append("<target>");
606 rpc.append("<").append(netconfTargetConfig).append("/>");
607 rpc.append("</target>");
608 rpc.append("<source>");
609 rpc.append(newConfiguration);
610 rpc.append("</source>");
611 rpc.append("</copy-config>");
612 rpc.append("</rpc>");
613 rpc.append(ENDPATTERN);
614 return checkReply(sendRequest(rpc.toString()));
615 }
616
617 @Override
618 public boolean deleteConfig(String netconfTargetConfig) throws NetconfException {
619 return deleteConfig(TargetConfig.toTargetConfig(netconfTargetConfig));
620 }
621
622 @Override
623 public boolean deleteConfig(TargetConfig netconfTargetConfig) throws NetconfException {
624 if (netconfTargetConfig.equals(TargetConfig.RUNNING)) {
625 log.warn("Target configuration for delete operation can't be \"running\"",
626 netconfTargetConfig);
627 return false;
628 }
629 StringBuilder rpc = new StringBuilder(XML_HEADER);
630 rpc.append("<rpc>");
631 rpc.append("<delete-config>");
632 rpc.append("<target>");
633 rpc.append("<").append(netconfTargetConfig).append("/>");
634 rpc.append("</target>");
635 rpc.append("</delete-config>");
636 rpc.append("</rpc>");
637 rpc.append(ENDPATTERN);
638 return checkReply(sendRequest(rpc.toString()));
639 }
640
641 @Override
642 public boolean lock(String configType) throws NetconfException {
643 StringBuilder rpc = new StringBuilder(XML_HEADER);
644 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
645 rpc.append("<lock>");
646 rpc.append("<target>");
647 rpc.append("<");
648 rpc.append(configType);
649 rpc.append("/>");
650 rpc.append("</target>");
651 rpc.append("</lock>");
652 rpc.append("</rpc>");
653 rpc.append(ENDPATTERN);
654 String lockReply = sendRequest(rpc.toString());
655 return checkReply(lockReply);
656 }
657
658 @Override
659 public boolean unlock(String configType) throws NetconfException {
660 StringBuilder rpc = new StringBuilder(XML_HEADER);
661 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
662 rpc.append("<unlock>");
663 rpc.append("<target>");
664 rpc.append("<");
665 rpc.append(configType);
666 rpc.append("/>");
667 rpc.append("</target>");
668 rpc.append("</unlock>");
669 rpc.append("</rpc>");
670 rpc.append(ENDPATTERN);
671 String unlockReply = sendRequest(rpc.toString());
672 return checkReply(unlockReply);
673 }
674
675 @Override
676 public boolean lock() throws NetconfException {
677 return lock("running");
678 }
679
680 @Override
681 public boolean unlock() throws NetconfException {
682 return unlock("running");
683 }
684
685 @Override
686 public boolean close() throws NetconfException {
687 return close(false);
688 }
689
690 private boolean close(boolean force) throws NetconfException {
691 StringBuilder rpc = new StringBuilder();
692 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
693 if (force) {
694 rpc.append("<kill-session/>");
695 } else {
696 rpc.append("<close-session/>");
697 }
698 rpc.append("</rpc>");
699 rpc.append(ENDPATTERN);
700 return checkReply(sendRequest(rpc.toString())) || close(true);
701 }
702
703 @Override
704 public String getSessionId() {
705 return sessionID;
706 }
707
708 @Override
709 public Set<String> getDeviceCapabilitiesSet() {
710 return Collections.unmodifiableSet(deviceCapabilities);
711 }
712
713 @Deprecated
714 @Override
715 public String getServerCapabilities() {
716 return serverHelloResponseOld;
717 }
718
719 @Deprecated
720 @Override
721 public void setDeviceCapabilities(List<String> capabilities) {
722 onosCapabilities = capabilities;
723 }
724
725 @Override
726 public void setOnosCapabilities(Iterable<String> capabilities) {
727 onosCapabilities = capabilities;
728 }
729
730
731 @Override
732 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
733 streamHandler.addDeviceEventListener(listener);
734 primaryListeners.add(listener);
735 }
736
737 @Override
738 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
739 primaryListeners.remove(listener);
740 streamHandler.removeDeviceEventListener(listener);
741 }
742
743 private boolean checkReply(String reply) throws NetconfException {
744 if (reply != null) {
745 if (!reply.contains("<rpc-error>")) {
746 log.debug("Device {} sent reply {}", deviceInfo, reply);
747 return true;
748 } else if (reply.contains("<ok/>")
749 || (reply.contains("<rpc-error>")
750 && reply.contains("warning"))) {
751 log.debug("Device {} sent reply {}", deviceInfo, reply);
752 return true;
753 }
754 }
755 log.warn("Device {} has error in reply {}", deviceInfo, reply);
756 return false;
757 }
758
759 static class NotificationSession extends NetconfSessionMinaImpl {
760
761 private String notificationFilter;
762
763 NotificationSession(NetconfDeviceInfo deviceInfo)
764 throws NetconfException {
765 super(deviceInfo);
766 }
767
768 @Override
769 protected void startSubscriptionStream(String filterSchema)
770 throws NetconfException {
771
772 notificationFilter = filterSchema;
773 requestSync(createSubscriptionString(filterSchema));
774 }
775
776 @Override
777 public String toString() {
778 return MoreObjects.toStringHelper(getClass())
779 .add("deviceInfo", deviceInfo)
780 .add("sessionID", getSessionId())
781 .add("notificationFilter", notificationFilter)
782 .toString();
783 }
784 }
785
786 /**
787 * Listener attached to child session for notification streaming.
788 * <p>
789 * Forwards all notification event from child session to primary session
790 * listeners.
791 */
792 private final class NotificationForwarder
793 implements NetconfDeviceOutputEventListener {
794
795 @Override
796 public boolean isRelevant(NetconfDeviceOutputEvent event) {
797 return event.type() == Type.DEVICE_NOTIFICATION;
798 }
799
800 @Override
801 public void event(NetconfDeviceOutputEvent event) {
802 primaryListeners.forEach(lsnr -> {
803 if (lsnr.isRelevant(event)) {
804 lsnr.event(event);
805 }
806 });
807 }
808 }
809
810 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
811
812 @Override
813 public void notify(NetconfDeviceOutputEvent event) {
814 Optional<Integer> messageId = event.getMessageID();
815 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
816 replies.keySet());
817 if (!messageId.isPresent()) {
818 errorReplies.add(event.getMessagePayload());
819 log.error("Device {} sent error reply {}",
820 event.getDeviceInfo(), event.getMessagePayload());
821 return;
822 }
823 CompletableFuture<String> completedReply =
824 replies.get(messageId.get());
825 if (completedReply != null) {
826 completedReply.complete(event.getMessagePayload());
827 }
828 }
829 }
830
831 public static class MinaSshNetconfSessionFactory implements NetconfSessionFactory {
832
833 @Override
834 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
835 return new NetconfSessionMinaImpl(netconfDeviceInfo);
836 }
837 }
838}