blob: dfb82cafcc489be066ea448e2a0527ed1bf52c37 [file] [log] [blame]
Andrea Campanella7bbe7b12017-05-03 16:03:38 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Andrea Campanella7bbe7b12017-05-03 16:03:38 -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.impl;
18
19import com.google.common.annotations.Beta;
20import com.google.common.base.MoreObjects;
21import com.google.common.base.Objects;
Kamil Stasiak9f59f442017-05-02 11:02:24 +020022import com.google.common.collect.ImmutableList;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070023import com.google.common.collect.ImmutableSet;
24import org.apache.sshd.client.SshClient;
25import org.apache.sshd.client.channel.ClientChannel;
26import org.apache.sshd.client.future.ConnectFuture;
27import org.apache.sshd.client.future.OpenFuture;
28import org.apache.sshd.client.session.ClientSession;
Sean Condon7347de92017-07-21 12:17:25 +010029import org.apache.sshd.common.FactoryManager;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070030import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
Holger Schulz092cbbf2017-08-31 17:52:30 +020031import org.bouncycastle.jce.provider.BouncyCastleProvider;
32import org.bouncycastle.openssl.PEMParser;
33import org.bouncycastle.openssl.PEMKeyPair;
34import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -070035import org.onosproject.netconf.DatastoreId;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070036import org.onosproject.netconf.NetconfDeviceInfo;
37import org.onosproject.netconf.NetconfDeviceOutputEvent;
38import org.onosproject.netconf.NetconfDeviceOutputEvent.Type;
39import org.onosproject.netconf.NetconfDeviceOutputEventListener;
40import org.onosproject.netconf.NetconfException;
41import org.onosproject.netconf.NetconfSession;
42import org.onosproject.netconf.NetconfSessionFactory;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
Yuta HIGUCHI15677982017-08-16 15:50:29 -070045
Holger Schulz092cbbf2017-08-31 17:52:30 +020046
Yuta HIGUCHI15677982017-08-16 15:50:29 -070047import static java.nio.charset.StandardCharsets.UTF_8;
48
Holger Schulz092cbbf2017-08-31 17:52:30 +020049import java.io.CharArrayReader;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070050import java.io.IOException;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070051import java.security.KeyFactory;
52import java.security.KeyPair;
53import java.security.NoSuchAlgorithmException;
54import java.security.PublicKey;
55import java.security.spec.InvalidKeySpecException;
56import java.security.spec.X509EncodedKeySpec;
Kamil Stasiak9f59f442017-05-02 11:02:24 +020057import java.util.LinkedHashSet;
58import java.util.Map;
59import java.util.Set;
60import java.util.List;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070061import java.util.Collection;
62import java.util.Collections;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070063import java.util.Optional;
Kamil Stasiak9f59f442017-05-02 11:02:24 +020064import java.util.ArrayList;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070065import java.util.concurrent.CompletableFuture;
66import java.util.concurrent.ConcurrentHashMap;
67import java.util.concurrent.CopyOnWriteArrayList;
68import java.util.concurrent.ExecutionException;
69import java.util.concurrent.TimeUnit;
70import java.util.concurrent.TimeoutException;
71import java.util.concurrent.atomic.AtomicInteger;
72import java.util.regex.Matcher;
73import java.util.regex.Pattern;
74
75/**
76 * Implementation of a NETCONF session to talk to a device.
77 */
78public class NetconfSessionMinaImpl implements NetconfSession {
79
80 private static final Logger log = LoggerFactory
81 .getLogger(NetconfSessionMinaImpl.class);
82
Yuta HIGUCHI371667d2017-09-05 17:30:51 -070083 /**
84 * NC 1.0, RFC4742 EOM sequence.
85 */
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070086 private static final String ENDPATTERN = "]]>]]>";
87 private static final String MESSAGE_ID_STRING = "message-id";
88 private static final String HELLO = "<hello";
89 private static final String NEW_LINE = "\n";
90 private static final String END_OF_RPC_OPEN_TAG = "\">";
91 private static final String EQUAL = "=";
92 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
93 private static final String RPC_OPEN = "<rpc ";
94 private static final String RPC_CLOSE = "</rpc>";
95 private static final String GET_OPEN = "<get>";
96 private static final String GET_CLOSE = "</get>";
97 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
98 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
99 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
100 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
101 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
102 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
103 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
104 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
105 private static final String TARGET_OPEN = "<target>";
106 private static final String TARGET_CLOSE = "</target>";
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700107 // FIXME hard coded namespace nc
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700108 private static final String CONFIG_OPEN = "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
109 private static final String CONFIG_CLOSE = "</config>";
110 private static final String XML_HEADER =
111 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
112 private static final String NETCONF_BASE_NAMESPACE =
113 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
114 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
115 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700116 // FIXME hard coded namespace base10
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700117 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
118 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
119
120 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
121
122 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
123 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
124
125 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
126 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200127 private static final String HASH = "#";
128 private static final String LF = "\n";
129 private static final String MSGLEN_REGEX_PATTERN = "\n#\\d+\n";
130 private static final String NETCONF_10_CAPABILITY = "urn:ietf:params:netconf:base:1.0";
131 private static final String NETCONF_11_CAPABILITY = "urn:ietf:params:netconf:base:1.1";
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700132
133 private String sessionID;
134 private final AtomicInteger messageIdInteger = new AtomicInteger(1);
135 protected final NetconfDeviceInfo deviceInfo;
136 private Iterable<String> onosCapabilities =
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200137 ImmutableList.of(NETCONF_10_CAPABILITY, NETCONF_11_CAPABILITY);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700138
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700139 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
140 private NetconfStreamHandler streamHandler;
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700141 // FIXME ONOS-7019 key type should be revised to a String, see RFC6241
142 /**
143 * Message-ID and corresponding Future waiting for response.
144 */
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700145 private Map<Integer, CompletableFuture<String>> replies;
Sean Condon7347de92017-07-21 12:17:25 +0100146 private List<String> errorReplies; // Not sure why we need this?
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700147 private boolean subscriptionConnected = false;
148 private String notificationFilterSchema = null;
149
150 private final Collection<NetconfDeviceOutputEventListener> primaryListeners =
151 new CopyOnWriteArrayList<>();
152 private final Collection<NetconfSession> children =
153 new CopyOnWriteArrayList<>();
154
Sean Condon54d82432017-07-26 22:27:25 +0100155 private int connectTimeout;
156 private int replyTimeout;
157 private int idleTimeout;
158
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700159
160 private ClientChannel channel = null;
161 private ClientSession session = null;
162 private SshClient client = null;
163
164
165 public NetconfSessionMinaImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
166 this.deviceInfo = deviceInfo;
167 replies = new ConcurrentHashMap<>();
168 errorReplies = new ArrayList<>();
Sean Condon54d82432017-07-26 22:27:25 +0100169
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700170 startConnection();
171 }
172
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200173 public NetconfSessionMinaImpl(NetconfDeviceInfo deviceInfo, List<String> capabilities) throws NetconfException {
174 this.deviceInfo = deviceInfo;
175 replies = new ConcurrentHashMap<>();
176 errorReplies = new ArrayList<>();
177 setOnosCapabilities(capabilities);
178 startConnection();
179 }
180
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700181 private void startConnection() throws NetconfException {
Sean Condon54d82432017-07-26 22:27:25 +0100182 connectTimeout = deviceInfo.getConnectTimeoutSec().orElse(
183 NetconfControllerImpl.netconfConnectTimeout);
184 replyTimeout = deviceInfo.getReplyTimeoutSec().orElse(
185 NetconfControllerImpl.netconfReplyTimeout);
186 idleTimeout = deviceInfo.getIdleTimeoutSec().orElse(
187 NetconfControllerImpl.netconfIdleTimeout);
188 log.info("Connecting to {} with timeouts C:{}, R:{}, I:{}", deviceInfo,
189 connectTimeout, replyTimeout, idleTimeout);
190
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700191 try {
192 startClient();
193 } catch (IOException e) {
194 throw new NetconfException("Failed to establish SSH with device " + deviceInfo, e);
195 }
196 }
197
198 private void startClient() throws IOException {
199 client = SshClient.setUpDefaultClient();
Sean Condon7347de92017-07-21 12:17:25 +0100200 client.getProperties().putIfAbsent(FactoryManager.IDLE_TIMEOUT,
Sean Condon54d82432017-07-26 22:27:25 +0100201 TimeUnit.SECONDS.toMillis(idleTimeout));
Sean Condon7347de92017-07-21 12:17:25 +0100202 client.getProperties().putIfAbsent(FactoryManager.NIO2_READ_TIMEOUT,
Sean Condon54d82432017-07-26 22:27:25 +0100203 TimeUnit.SECONDS.toMillis(idleTimeout + 15L));
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700204 client.start();
205 client.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
206 startSession();
207 }
208
209 private void startSession() throws IOException {
210 final ConnectFuture connectFuture;
211 connectFuture = client.connect(deviceInfo.name(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200212 deviceInfo.ip().toString(),
213 deviceInfo.port())
Sean Condon54d82432017-07-26 22:27:25 +0100214 .verify(connectTimeout, TimeUnit.SECONDS);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700215 session = connectFuture.getSession();
216 //Using the device ssh key if possible
217 if (deviceInfo.getKey() != null) {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700218 try (PEMParser pemParser = new PEMParser(new CharArrayReader(deviceInfo.getKey()))) {
219 JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
220 try {
221 KeyPair kp = converter.getKeyPair((PEMKeyPair) pemParser.readObject());
222 session.addPublicKeyIdentity(kp);
223 } catch (IOException e) {
224 throw new NetconfException("Failed to authenticate session with device " +
225 deviceInfo + "check key to be a valid key", e);
226 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700227 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700228 } else {
229 session.addPasswordIdentity(deviceInfo.password());
230 }
Sean Condon54d82432017-07-26 22:27:25 +0100231 session.auth().verify(connectTimeout, TimeUnit.SECONDS);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700232 Set<ClientSession.ClientSessionEvent> event = session.waitFor(
233 ImmutableSet.of(ClientSession.ClientSessionEvent.WAIT_AUTH,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200234 ClientSession.ClientSessionEvent.CLOSED,
235 ClientSession.ClientSessionEvent.AUTHED), 0);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700236
237 if (!event.contains(ClientSession.ClientSessionEvent.AUTHED)) {
238 log.debug("Session closed {} {}", event, session.isClosed());
239 throw new NetconfException("Failed to authenticate session with device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200240 deviceInfo + "check the user/pwd or key");
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700241 }
242 openChannel();
243 }
244
245 private PublicKey getPublicKey(byte[] keyBytes, String type)
246 throws NoSuchAlgorithmException, InvalidKeySpecException {
247
248 X509EncodedKeySpec spec =
249 new X509EncodedKeySpec(keyBytes);
250 KeyFactory kf = KeyFactory.getInstance(type);
251 return kf.generatePublic(spec);
252 }
253
254 private void openChannel() throws IOException {
255 channel = session.createSubsystemChannel("netconf");
256 OpenFuture channelFuture = channel.open();
Sean Condon54d82432017-07-26 22:27:25 +0100257 if (channelFuture.await(connectTimeout, TimeUnit.SECONDS)) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700258 if (channelFuture.isOpened()) {
259 streamHandler = new NetconfStreamThread(channel.getInvertedOut(), channel.getInvertedIn(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200260 channel.getInvertedErr(), deviceInfo,
261 new NetconfSessionDelegateImpl(), replies);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700262 } else {
263 throw new NetconfException("Failed to open channel with device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200264 deviceInfo);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700265 }
266 sendHello();
267 }
268 }
269
270
271 @Beta
272 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
273 boolean openNewSession = false;
274 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
275 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
276 openNewSession = true;
277
278 } else if (subscriptionConnected &&
279 notificationFilterSchema != null &&
280 !Objects.equal(filterSchema, notificationFilterSchema)) {
281 // interleave supported and existing filter is NOT "no filtering"
282 // and was requested with different filtering schema
283 log.info("Cannot use existing session for subscription {} ({})",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200284 deviceInfo, filterSchema);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700285 openNewSession = true;
286 }
287
288 if (openNewSession) {
289 log.info("Creating notification session to {} with filter {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200290 deviceInfo, filterSchema);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700291 NetconfSession child = new NotificationSession(deviceInfo);
292
293 child.addDeviceOutputListener(new NotificationForwarder());
294
295 child.startSubscription(filterSchema);
296 children.add(child);
297 return;
298 }
299
300 // request to start interleaved notification session
301 String reply = sendRequest(createSubscriptionString(filterSchema));
302 if (!checkReply(reply)) {
303 throw new NetconfException("Subscription not successful with device "
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200304 + deviceInfo + " with reply " + reply);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700305 }
306 subscriptionConnected = true;
307 }
308
309 @Override
310 public void startSubscription() throws NetconfException {
311 if (!subscriptionConnected) {
312 startSubscriptionStream(null);
313 }
314 streamHandler.setEnableNotifications(true);
315 }
316
317 @Beta
318 @Override
319 public void startSubscription(String filterSchema) throws NetconfException {
320 if (!subscriptionConnected) {
321 notificationFilterSchema = filterSchema;
322 startSubscriptionStream(filterSchema);
323 }
324 streamHandler.setEnableNotifications(true);
325 }
326
327 @Beta
328 protected String createSubscriptionString(String filterSchema) {
329 StringBuilder subscriptionbuffer = new StringBuilder();
330 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
331 subscriptionbuffer.append(" <create-subscription\n");
332 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
333 // FIXME Only subtree filtering supported at the moment.
334 if (filterSchema != null) {
335 subscriptionbuffer.append(" ");
336 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
337 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
338 subscriptionbuffer.append(" ");
339 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
340 }
341 subscriptionbuffer.append(" </create-subscription>\n");
342 subscriptionbuffer.append("</rpc>\n");
343 subscriptionbuffer.append(ENDPATTERN);
344 return subscriptionbuffer.toString();
345 }
346
347 @Override
348 public void endSubscription() throws NetconfException {
349 if (subscriptionConnected) {
350 streamHandler.setEnableNotifications(false);
351 } else {
352 throw new NetconfException("Subscription does not exist.");
353 }
354 }
355
356 private void sendHello() throws NetconfException {
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700357 String serverHelloResponse = sendRequest(createHelloString(), true);
358 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponse);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700359 while (capabilityMatcher.find()) {
360 deviceCapabilities.add(capabilityMatcher.group(1));
361 }
362 sessionID = String.valueOf(-1);
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700363 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponse);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700364 if (sessionIDMatcher.find()) {
365 sessionID = sessionIDMatcher.group(1);
366 } else {
367 throw new NetconfException("Missing SessionID in server hello " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200368 "reponse.");
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700369 }
370
371 }
372
373 private String createHelloString() {
374 StringBuilder hellobuffer = new StringBuilder();
375 hellobuffer.append(XML_HEADER);
376 hellobuffer.append("\n");
377 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
378 hellobuffer.append(" <capabilities>\n");
379 onosCapabilities.forEach(
380 cap -> hellobuffer.append(" <capability>")
381 .append(cap)
382 .append("</capability>\n"));
383 hellobuffer.append(" </capabilities>\n");
384 hellobuffer.append("</hello>\n");
385 hellobuffer.append(ENDPATTERN);
386 return hellobuffer.toString();
387
388 }
389
390 @Override
391 public void checkAndReestablish() throws NetconfException {
392 try {
393 if (client.isClosed()) {
394 log.debug("Trying to restart the whole SSH connection with {}", deviceInfo.getDeviceId());
395 cleanUp();
396 startConnection();
397 } else if (session.isClosed()) {
398 log.debug("Trying to restart the session with {}", session, deviceInfo.getDeviceId());
399 cleanUp();
400 startSession();
401 } else if (channel.isClosed()) {
402 log.debug("Trying to reopen the channel with {}", deviceInfo.getDeviceId());
403 cleanUp();
404 openChannel();
405 }
406 if (subscriptionConnected) {
407 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
408 subscriptionConnected = false;
409 startSubscription(notificationFilterSchema);
410 }
Sean Condon7347de92017-07-21 12:17:25 +0100411 } catch (IOException | IllegalStateException e) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700412 log.error("Can't reopen connection for device {}", e.getMessage());
413 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
414 }
415 }
416
417 private void cleanUp() {
418 //makes sure everything is at a clean state.
419 replies.clear();
420 }
421
422 @Override
423 public String requestSync(String request) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700424 String reply = sendRequest(request);
425 checkReply(reply);
426 return reply;
427 }
428
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200429
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700430 // FIXME rename to align with what it actually do
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200431 /**
432 * Validate and format netconf message.
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700433 * - NC1.0 if no EOM sequence present on {@code message}, append.
434 * - NC1.1 chunk-encode given message unless it already is chunk encoded
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200435 *
436 * @param message to format
437 * @return formated message
438 */
439 private String formatNetconfMessage(String message) {
440 if (deviceCapabilities.contains(NETCONF_11_CAPABILITY)) {
441 message = formatChunkedMessage(message);
442 } else {
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700443 if (!message.endsWith(ENDPATTERN)) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200444 message = message + NEW_LINE + ENDPATTERN;
445 }
446 }
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700447 return message;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200448 }
449
450 /**
451 * Validate and format message according to chunked framing mechanism.
452 *
453 * @param message to format
454 * @return formated message
455 */
456 private String formatChunkedMessage(String message) {
457 if (message.endsWith(ENDPATTERN)) {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700458 // message given had Netconf 1.0 EOM pattern -> remove
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200459 message = message.substring(0, message.length() - ENDPATTERN.length());
460 }
461 if (!message.startsWith(LF + HASH)) {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700462 // chunk encode message
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700463 message = LF + HASH + message.getBytes(UTF_8).length + LF + message + LF + HASH + HASH + LF;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200464 }
465 return message;
466 }
467
468
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700469 @Override
470 @Deprecated
471 public CompletableFuture<String> request(String request) {
472 return streamHandler.sendMessage(request);
473 }
474
Sean Condon54d82432017-07-26 22:27:25 +0100475 @Override
476 public int timeoutConnectSec() {
477 return connectTimeout;
478 }
479
480 @Override
481 public int timeoutReplySec() {
482 return replyTimeout;
483 }
484
485 @Override
486 public int timeoutIdleSec() {
487 return idleTimeout;
488 }
489
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700490 private CompletableFuture<String> request(String request, int messageId) {
491 return streamHandler.sendMessage(request, messageId);
492 }
493
494 private String sendRequest(String request) throws NetconfException {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700495 // FIXME probably chunk-encoding too early
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200496 request = formatNetconfMessage(request);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700497 return sendRequest(request, false);
498 }
499
500 private String sendRequest(String request, boolean isHello) throws NetconfException {
501 checkAndReestablish();
502 int messageId = -1;
503 if (!isHello) {
504 messageId = messageIdInteger.getAndIncrement();
505 }
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700506 // FIXME potentially re-writing chunked encoded String?
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700507 request = formatXmlHeader(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200508 request = formatRequestMessageId(request, messageId);
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700509 log.debug("Sending request to NETCONF with timeout {} for {}",
510 replyTimeout, deviceInfo.name());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700511 CompletableFuture<String> futureReply = request(request, messageId);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700512 String rp;
513 try {
514 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condon7347de92017-07-21 12:17:25 +0100515 replies.remove(messageId); // Why here???
516 } catch (InterruptedException e) {
517 Thread.currentThread().interrupt();
518 throw new NetconfException("Interrupted waiting for reply for request" + request, e);
519 } catch (TimeoutException e) {
Sean Condon54d82432017-07-26 22:27:25 +0100520 throw new NetconfException("Timed out waiting for reply for request " +
521 request + " after " + replyTimeout + " sec.", e);
Sean Condon7347de92017-07-21 12:17:25 +0100522 } catch (ExecutionException e) {
523 log.warn("Closing session {} for {} due to unexpected Error", sessionID, deviceInfo, e);
524 try {
525 session.close();
526 channel.close(); //Closes the socket which should interrupt NetconfStreamThread
527 client.close();
528 } catch (IOException ioe) {
529 log.warn("Error closing session {} on {}", sessionID, deviceInfo, ioe);
530 }
531 NetconfDeviceOutputEvent event = new NetconfDeviceOutputEvent(
532 NetconfDeviceOutputEvent.Type.SESSION_CLOSED,
533 null, "Closed due to unexpected error " + e.getCause(),
534 Optional.of(-1), deviceInfo);
535 publishEvent(event);
536 errorReplies.clear(); // move to cleanUp()?
537 cleanUp();
538
539 throw new NetconfException("Closing session " + sessionID + " for " + deviceInfo +
540 " for request " + request, e);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700541 }
542 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
543 return rp.trim();
544 }
545
546 private String formatRequestMessageId(String request, int messageId) {
547 if (request.contains(MESSAGE_ID_STRING)) {
548 //FIXME if application provides his own counting of messages this fails that count
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700549 // FIXME assumes message-id is integer. RFC6241 allows anything as long as it is allowed in XML
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700550 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200551 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700552 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
553 //FIXME find out a better way to enforce the presence of message-id
554 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
555 + messageId + "\"" + ">");
556 }
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700557 request = updateRequestLength(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200558 return request;
559 }
560
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700561 private String updateRequestLength(String request) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200562 if (request.contains(LF + HASH + HASH + LF)) {
563 int oldLen = Integer.parseInt(request.split(HASH)[1].split(LF)[0]);
564 String rpcWithEnding = request.substring(request.indexOf('<'));
565 String firstBlock = request.split(MSGLEN_REGEX_PATTERN)[1].split(LF + HASH + HASH + LF)[0];
566 int newLen = 0;
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700567 newLen = firstBlock.getBytes(UTF_8).length;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200568 if (oldLen != newLen) {
569 return LF + HASH + newLen + LF + rpcWithEnding;
570 }
571 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700572 return request;
573 }
574
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700575 /**
576 * Ensures xml start directive/declaration appears in the {@code request}.
577 * @param request RPC request message
578 * @return XML RPC message
579 */
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700580 private String formatXmlHeader(String request) {
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700581 if (!request.startsWith(XML_HEADER)) {
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700582 //FIXME if application provides his own XML header of different type there is a clash
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200583 if (request.startsWith(LF + HASH)) {
584 request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
585 } else {
586 request = XML_HEADER + "\n" + request;
587 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700588 }
589 return request;
590 }
591
592 @Override
593 public String doWrappedRpc(String request) throws NetconfException {
594 StringBuilder rpc = new StringBuilder(XML_HEADER);
595 rpc.append(RPC_OPEN);
596 rpc.append(MESSAGE_ID_STRING);
597 rpc.append(EQUAL);
598 rpc.append("\"");
599 rpc.append(messageIdInteger.get());
600 rpc.append("\" ");
601 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
602 rpc.append(request);
603 rpc.append(RPC_CLOSE).append(NEW_LINE);
604 rpc.append(ENDPATTERN);
605 String reply = sendRequest(rpc.toString());
606 checkReply(reply);
607 return reply;
608 }
609
610 @Override
611 public String get(String request) throws NetconfException {
612 return requestSync(request);
613 }
614
615 @Override
616 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
617 StringBuilder rpc = new StringBuilder(XML_HEADER);
618 rpc.append(RPC_OPEN);
619 rpc.append(MESSAGE_ID_STRING);
620 rpc.append(EQUAL);
621 rpc.append("\"");
622 rpc.append(messageIdInteger.get());
623 rpc.append("\" ");
624 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
625 rpc.append(GET_OPEN).append(NEW_LINE);
626 if (filterSchema != null) {
627 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
628 rpc.append(filterSchema).append(NEW_LINE);
629 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
630 }
631 if (withDefaultsMode != null) {
632 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
633 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
634 }
635 rpc.append(GET_CLOSE).append(NEW_LINE);
636 rpc.append(RPC_CLOSE).append(NEW_LINE);
637 rpc.append(ENDPATTERN);
638 String reply = sendRequest(rpc.toString());
639 checkReply(reply);
640 return reply;
641 }
642
643 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700644 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700645 return getConfig(netconfTargetConfig, null);
646 }
647
648 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700649 public String getConfig(DatastoreId netconfTargetConfig,
650 String configurationSchema) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700651 StringBuilder rpc = new StringBuilder(XML_HEADER);
652 rpc.append("<rpc ");
653 rpc.append(MESSAGE_ID_STRING);
654 rpc.append(EQUAL);
655 rpc.append("\"");
656 rpc.append(messageIdInteger.get());
657 rpc.append("\" ");
658 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
659 rpc.append("<get-config>\n");
660 rpc.append("<source>\n");
661 rpc.append("<").append(netconfTargetConfig).append("/>");
662 rpc.append("</source>");
663 if (configurationSchema != null) {
664 rpc.append("<filter type=\"subtree\">\n");
665 rpc.append(configurationSchema).append("\n");
666 rpc.append("</filter>\n");
667 }
668 rpc.append("</get-config>\n");
669 rpc.append("</rpc>\n");
670 rpc.append(ENDPATTERN);
671 String reply = sendRequest(rpc.toString());
672 return checkReply(reply) ? reply : "ERROR " + reply;
673 }
674
675 @Override
676 public boolean editConfig(String newConfiguration) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200677 if (!newConfiguration.endsWith(ENDPATTERN)) {
678 newConfiguration = newConfiguration + ENDPATTERN;
679 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700680 return checkReply(sendRequest(newConfiguration));
681 }
682
683 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700684 public boolean editConfig(DatastoreId netconfTargetConfig,
685 String mode,
686 String newConfiguration)
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700687 throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700688
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700689 newConfiguration = newConfiguration.trim();
690 StringBuilder rpc = new StringBuilder(XML_HEADER);
691 rpc.append(RPC_OPEN);
692 rpc.append(MESSAGE_ID_STRING);
693 rpc.append(EQUAL);
694 rpc.append("\"");
695 rpc.append(messageIdInteger.get());
696 rpc.append("\" ");
697 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
698 rpc.append(EDIT_CONFIG_OPEN).append("\n");
699 rpc.append(TARGET_OPEN);
700 rpc.append("<").append(netconfTargetConfig).append("/>");
701 rpc.append(TARGET_CLOSE).append("\n");
702 if (mode != null) {
703 rpc.append(DEFAULT_OPERATION_OPEN);
704 rpc.append(mode);
705 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
706 }
707 rpc.append(CONFIG_OPEN).append("\n");
708 rpc.append(newConfiguration);
709 rpc.append(CONFIG_CLOSE).append("\n");
710 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
711 rpc.append(RPC_CLOSE);
712 rpc.append(ENDPATTERN);
713 log.debug(rpc.toString());
714 String reply = sendRequest(rpc.toString());
715 return checkReply(reply);
716 }
717
718 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700719 public boolean copyConfig(DatastoreId destination,
720 DatastoreId source)
721 throws NetconfException {
722 return bareCopyConfig(destination.asXml(), source.asXml());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700723 }
724
725 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700726 public boolean copyConfig(DatastoreId netconfTargetConfig,
727 String newConfiguration)
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700728 throws NetconfException {
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700729 return bareCopyConfig(netconfTargetConfig.asXml(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200730 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700731 }
732
733 @Override
734 public boolean copyConfig(String netconfTargetConfig,
735 String newConfiguration) throws NetconfException {
736 return bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200737 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700738 }
739
740 /**
741 * Normalize String parameter passed to copy-config API.
742 * <p>
743 * Provided for backward compatibility purpose
744 *
745 * @param input passed to copyConfig API
746 * @return XML likely to be suitable for copy-config source or target
747 */
748 private static CharSequence normalizeCopyConfigParam(String input) {
749 input = input.trim();
750 if (input.startsWith("<url")) {
751 return input;
752 } else if (!input.startsWith("<")) {
753 // assume it is a datastore name
754 return DatastoreId.datastore(input).asXml();
755 } else if (!input.startsWith("<config>")) {
756 return "<config>" + input + "</config>";
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700757 }
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700758 return input;
759 }
760
761 private boolean bareCopyConfig(CharSequence target,
762 CharSequence source)
763 throws NetconfException {
764
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700765 StringBuilder rpc = new StringBuilder(XML_HEADER);
766 rpc.append(RPC_OPEN);
767 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
768 rpc.append("<copy-config>");
769 rpc.append("<target>");
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700770 rpc.append(target);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700771 rpc.append("</target>");
772 rpc.append("<source>");
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700773 rpc.append(source);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700774 rpc.append("</source>");
775 rpc.append("</copy-config>");
776 rpc.append("</rpc>");
777 rpc.append(ENDPATTERN);
778 return checkReply(sendRequest(rpc.toString()));
779 }
780
781 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700782 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
783 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700784 log.warn("Target configuration for delete operation can't be \"running\"",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200785 netconfTargetConfig);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700786 return false;
787 }
788 StringBuilder rpc = new StringBuilder(XML_HEADER);
789 rpc.append("<rpc>");
790 rpc.append("<delete-config>");
791 rpc.append("<target>");
792 rpc.append("<").append(netconfTargetConfig).append("/>");
793 rpc.append("</target>");
794 rpc.append("</delete-config>");
795 rpc.append("</rpc>");
796 rpc.append(ENDPATTERN);
797 return checkReply(sendRequest(rpc.toString()));
798 }
799
800 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700801 public boolean lock(DatastoreId configType) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700802 StringBuilder rpc = new StringBuilder(XML_HEADER);
803 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
804 rpc.append("<lock>");
805 rpc.append("<target>");
806 rpc.append("<");
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700807 rpc.append(configType.id());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700808 rpc.append("/>");
809 rpc.append("</target>");
810 rpc.append("</lock>");
811 rpc.append("</rpc>");
812 rpc.append(ENDPATTERN);
813 String lockReply = sendRequest(rpc.toString());
814 return checkReply(lockReply);
815 }
816
817 @Override
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700818 public boolean unlock(DatastoreId configType) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700819 StringBuilder rpc = new StringBuilder(XML_HEADER);
820 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
821 rpc.append("<unlock>");
822 rpc.append("<target>");
823 rpc.append("<");
Yuta HIGUCHI26c397c2017-05-19 12:52:28 -0700824 rpc.append(configType.id());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700825 rpc.append("/>");
826 rpc.append("</target>");
827 rpc.append("</unlock>");
828 rpc.append("</rpc>");
829 rpc.append(ENDPATTERN);
830 String unlockReply = sendRequest(rpc.toString());
831 return checkReply(unlockReply);
832 }
833
834 @Override
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700835 public boolean close() throws NetconfException {
836 return close(false);
837 }
838
839 private boolean close(boolean force) throws NetconfException {
840 StringBuilder rpc = new StringBuilder();
841 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
842 if (force) {
843 rpc.append("<kill-session/>");
844 } else {
845 rpc.append("<close-session/>");
846 }
847 rpc.append("</rpc>");
848 rpc.append(ENDPATTERN);
849 return checkReply(sendRequest(rpc.toString())) || close(true);
850 }
851
852 @Override
853 public String getSessionId() {
854 return sessionID;
855 }
856
857 @Override
858 public Set<String> getDeviceCapabilitiesSet() {
859 return Collections.unmodifiableSet(deviceCapabilities);
860 }
861
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700862 @Override
863 public void setOnosCapabilities(Iterable<String> capabilities) {
864 onosCapabilities = capabilities;
865 }
866
867
868 @Override
869 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
870 streamHandler.addDeviceEventListener(listener);
871 primaryListeners.add(listener);
872 }
873
874 @Override
875 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
876 primaryListeners.remove(listener);
877 streamHandler.removeDeviceEventListener(listener);
878 }
879
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700880 private boolean checkReply(String reply) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700881 if (reply != null) {
882 if (!reply.contains("<rpc-error>")) {
883 log.debug("Device {} sent reply {}", deviceInfo, reply);
884 return true;
885 } else if (reply.contains("<ok/>")
886 || (reply.contains("<rpc-error>")
887 && reply.contains("warning"))) {
888 log.debug("Device {} sent reply {}", deviceInfo, reply);
889 return true;
890 }
891 }
892 log.warn("Device {} has error in reply {}", deviceInfo, reply);
893 return false;
894 }
895
Sean Condon7347de92017-07-21 12:17:25 +0100896 protected void publishEvent(NetconfDeviceOutputEvent event) {
897 primaryListeners.forEach(lsnr -> {
898 if (lsnr.isRelevant(event)) {
899 lsnr.event(event);
900 }
901 });
902 }
903
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700904 static class NotificationSession extends NetconfSessionMinaImpl {
905
906 private String notificationFilter;
907
908 NotificationSession(NetconfDeviceInfo deviceInfo)
909 throws NetconfException {
910 super(deviceInfo);
911 }
912
913 @Override
914 protected void startSubscriptionStream(String filterSchema)
915 throws NetconfException {
916
917 notificationFilter = filterSchema;
918 requestSync(createSubscriptionString(filterSchema));
919 }
920
921 @Override
922 public String toString() {
923 return MoreObjects.toStringHelper(getClass())
924 .add("deviceInfo", deviceInfo)
925 .add("sessionID", getSessionId())
926 .add("notificationFilter", notificationFilter)
927 .toString();
928 }
929 }
930
931 /**
932 * Listener attached to child session for notification streaming.
933 * <p>
934 * Forwards all notification event from child session to primary session
935 * listeners.
936 */
937 private final class NotificationForwarder
938 implements NetconfDeviceOutputEventListener {
939
940 @Override
941 public boolean isRelevant(NetconfDeviceOutputEvent event) {
942 return event.type() == Type.DEVICE_NOTIFICATION;
943 }
944
945 @Override
946 public void event(NetconfDeviceOutputEvent event) {
Sean Condon7347de92017-07-21 12:17:25 +0100947 publishEvent(event);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700948 }
949 }
950
951 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
952
953 @Override
954 public void notify(NetconfDeviceOutputEvent event) {
955 Optional<Integer> messageId = event.getMessageID();
956 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200957 replies.keySet());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700958 if (!messageId.isPresent()) {
959 errorReplies.add(event.getMessagePayload());
960 log.error("Device {} sent error reply {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200961 event.getDeviceInfo(), event.getMessagePayload());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700962 return;
963 }
964 CompletableFuture<String> completedReply =
Sean Condon7347de92017-07-21 12:17:25 +0100965 replies.get(messageId.get()); // remove(..)?
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700966 if (completedReply != null) {
967 completedReply.complete(event.getMessagePayload());
968 }
969 }
970 }
971
972 public static class MinaSshNetconfSessionFactory implements NetconfSessionFactory {
973
974 @Override
975 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
976 return new NetconfSessionMinaImpl(netconfDeviceInfo);
977 }
978 }
Sean Condon54d82432017-07-26 22:27:25 +0100979}