blob: 22bb2b8ed03057c033400ba72bd360580ef92e0a [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;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070024import com.google.common.collect.Sets;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070025import org.apache.sshd.client.SshClient;
26import org.apache.sshd.client.channel.ClientChannel;
27import org.apache.sshd.client.future.ConnectFuture;
28import org.apache.sshd.client.future.OpenFuture;
29import org.apache.sshd.client.session.ClientSession;
Sean Condon7347de92017-07-21 12:17:25 +010030import org.apache.sshd.common.FactoryManager;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070031import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
Holger Schulz092cbbf2017-08-31 17:52:30 +020032import org.bouncycastle.jce.provider.BouncyCastleProvider;
Holger Schulz092cbbf2017-08-31 17:52:30 +020033import org.bouncycastle.openssl.PEMKeyPair;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070034import org.bouncycastle.openssl.PEMParser;
Holger Schulz092cbbf2017-08-31 17:52:30 +020035import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070036import org.onlab.osgi.DefaultServiceDirectory;
37import org.onlab.osgi.ServiceDirectory;
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +010038import org.onlab.util.ItemNotFoundException;
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -070039import org.onlab.util.SharedExecutors;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070040import org.onosproject.net.DeviceId;
41import org.onosproject.net.driver.Driver;
42import org.onosproject.net.driver.DriverService;
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -070043import org.onosproject.netconf.AbstractNetconfSession;
gyewan.an91d7e7e2019-01-17 15:12:48 +090044import org.onosproject.netconf.NetconfController;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070045import org.onosproject.netconf.NetconfDeviceInfo;
46import org.onosproject.netconf.NetconfDeviceOutputEvent;
47import org.onosproject.netconf.NetconfDeviceOutputEvent.Type;
48import org.onosproject.netconf.NetconfDeviceOutputEventListener;
49import org.onosproject.netconf.NetconfException;
50import org.onosproject.netconf.NetconfSession;
51import org.onosproject.netconf.NetconfSessionFactory;
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -070052import org.onosproject.netconf.NetconfTransportException;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070053import org.slf4j.Logger;
Yuta HIGUCHI15677982017-08-16 15:50:29 -070054
Anurag Chadha8a9feb52020-07-28 18:35:05 +053055import java.io.FileReader;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070056import java.io.IOException;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070057import java.security.KeyFactory;
58import java.security.KeyPair;
59import java.security.NoSuchAlgorithmException;
60import java.security.PublicKey;
61import java.security.spec.InvalidKeySpecException;
62import java.security.spec.X509EncodedKeySpec;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070063import java.util.ArrayList;
64import java.util.Arrays;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070065import java.util.Collection;
66import java.util.Collections;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070067import java.util.LinkedHashSet;
68import java.util.List;
69import java.util.Map;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070070import java.util.Optional;
quan PHAM VAN32d70e52018-08-01 17:35:30 -070071import java.util.Set;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070072import java.util.concurrent.CompletableFuture;
73import java.util.concurrent.ConcurrentHashMap;
74import java.util.concurrent.CopyOnWriteArrayList;
75import java.util.concurrent.ExecutionException;
76import java.util.concurrent.TimeUnit;
77import java.util.concurrent.TimeoutException;
78import java.util.concurrent.atomic.AtomicInteger;
79import java.util.regex.Matcher;
80import java.util.regex.Pattern;
81
quan PHAM VAN32d70e52018-08-01 17:35:30 -070082import static java.nio.charset.StandardCharsets.UTF_8;
83import static org.slf4j.LoggerFactory.getLogger;
84
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070085/**
86 * Implementation of a NETCONF session to talk to a device.
87 */
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -070088public class NetconfSessionMinaImpl extends AbstractNetconfSession {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070089
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -070090 private static final Logger log = getLogger(NetconfSessionMinaImpl.class);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070091
Yuta HIGUCHI371667d2017-09-05 17:30:51 -070092 /**
93 * NC 1.0, RFC4742 EOM sequence.
94 */
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070095 private static final String ENDPATTERN = "]]>]]>";
96 private static final String MESSAGE_ID_STRING = "message-id";
97 private static final String HELLO = "<hello";
98 private static final String NEW_LINE = "\n";
99 private static final String END_OF_RPC_OPEN_TAG = "\">";
100 private static final String EQUAL = "=";
101 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700102 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700103 // FIXME hard coded namespace nc
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700104 private static final String XML_HEADER =
105 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700106
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700107 // FIXME hard coded namespace base10
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700108 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
109 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
110
111 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
112
113 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
114 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
115
116 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
117 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200118 private static final String HASH = "#";
119 private static final String LF = "\n";
120 private static final String MSGLEN_REGEX_PATTERN = "\n#\\d+\n";
121 private static final String NETCONF_10_CAPABILITY = "urn:ietf:params:netconf:base:1.0";
122 private static final String NETCONF_11_CAPABILITY = "urn:ietf:params:netconf:base:1.1";
quan PHAM VAN32d70e52018-08-01 17:35:30 -0700123 private static final String NETCONF_CLIENT_CAPABILITY = "netconfClientCapability";
Laszlo Pappbdb64082018-09-11 12:21:29 +0100124 private static final String NOTIFICATION_STREAM = "notificationStream";
Anurag Chadha8a9feb52020-07-28 18:35:05 +0530125 private static final String SSH_KEY_PATH = "/root/.ssh/id_rsa";
126 private static final String EMPTY_STRING = "";
quan PHAM VAN32d70e52018-08-01 17:35:30 -0700127
128 private static ServiceDirectory directory = new DefaultServiceDirectory();
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700129
130 private String sessionID;
131 private final AtomicInteger messageIdInteger = new AtomicInteger(1);
132 protected final NetconfDeviceInfo deviceInfo;
133 private Iterable<String> onosCapabilities =
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200134 ImmutableList.of(NETCONF_10_CAPABILITY, NETCONF_11_CAPABILITY);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700135
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700136 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
137 private NetconfStreamHandler streamHandler;
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700138 // FIXME ONOS-7019 key type should be revised to a String, see RFC6241
139 /**
140 * Message-ID and corresponding Future waiting for response.
141 */
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700142 private Map<Integer, CompletableFuture<String>> replies;
Sean Condon7347de92017-07-21 12:17:25 +0100143 private List<String> errorReplies; // Not sure why we need this?
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700144 private boolean subscriptionConnected = false;
145 private String notificationFilterSchema = null;
146
147 private final Collection<NetconfDeviceOutputEventListener> primaryListeners =
148 new CopyOnWriteArrayList<>();
149 private final Collection<NetconfSession> children =
150 new CopyOnWriteArrayList<>();
151
Sean Condon54d82432017-07-26 22:27:25 +0100152 private int connectTimeout;
153 private int replyTimeout;
154 private int idleTimeout;
155
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700156 private ClientChannel channel = null;
157 private ClientSession session = null;
158 private SshClient client = null;
159
DongRyeol Chac29f9072018-11-06 14:05:56 +0900160 private boolean disconnected = false;
161
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700162 public NetconfSessionMinaImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
163 this.deviceInfo = deviceInfo;
164 replies = new ConcurrentHashMap<>();
165 errorReplies = new ArrayList<>();
quan PHAM VAN32d70e52018-08-01 17:35:30 -0700166 Set<String> capabilities = getClientCapabilites(deviceInfo.getDeviceId());
167 if (!capabilities.isEmpty()) {
168 capabilities.addAll(Sets.newHashSet(onosCapabilities));
169 setOnosCapabilities(capabilities);
170 }
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700171 // FIXME should not immediately start session on construction
172 // setOnosCapabilities() is useless due to this behavior
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700173 startConnection();
174 }
175
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200176 public NetconfSessionMinaImpl(NetconfDeviceInfo deviceInfo, List<String> capabilities) throws NetconfException {
177 this.deviceInfo = deviceInfo;
178 replies = new ConcurrentHashMap<>();
179 errorReplies = new ArrayList<>();
180 setOnosCapabilities(capabilities);
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700181 // FIXME should not immediately start session on construction
182 // setOnosCapabilities() is useless due to this behavior
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200183 startConnection();
184 }
185
quan PHAM VAN32d70e52018-08-01 17:35:30 -0700186 /**
187 * Get the list of the netconf client capabilities from device driver property.
188 *
189 * @param deviceId the deviceID for which to recover the capabilities from the driver.
190 * @return the String list of clientCapability property, or null if it is not configured
191 */
192 public Set<String> getClientCapabilites(DeviceId deviceId) {
193 Set<String> capabilities = new LinkedHashSet<>();
194 DriverService driverService = directory.get(DriverService.class);
Andrea Campanellaa2a6c3c2018-12-11 12:56:38 +0100195 try {
196 Driver driver = driverService.getDriver(deviceId);
197 if (driver == null) {
198 return capabilities;
199 }
200 String clientCapabilities = driver.getProperty(NETCONF_CLIENT_CAPABILITY);
201 if (clientCapabilities == null) {
202 return capabilities;
203 }
204 String[] textStr = clientCapabilities.split("\\|");
205 capabilities.addAll(Arrays.asList(textStr));
206 return capabilities;
207 } catch (ItemNotFoundException e) {
208 log.warn("Driver for device {} currently not available", deviceId);
quan PHAM VAN32d70e52018-08-01 17:35:30 -0700209 return capabilities;
210 }
quan PHAM VAN32d70e52018-08-01 17:35:30 -0700211 }
212
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700213 private void startConnection() throws NetconfException {
Sean Condon54d82432017-07-26 22:27:25 +0100214 connectTimeout = deviceInfo.getConnectTimeoutSec().orElse(
Andrea Campanella3a361452019-08-02 10:17:53 +0200215 NetconfControllerImpl.netconfConnectTimeout);
Sean Condon54d82432017-07-26 22:27:25 +0100216 replyTimeout = deviceInfo.getReplyTimeoutSec().orElse(
Andrea Campanella3a361452019-08-02 10:17:53 +0200217 NetconfControllerImpl.netconfReplyTimeout);
Sean Condon54d82432017-07-26 22:27:25 +0100218 idleTimeout = deviceInfo.getIdleTimeoutSec().orElse(
Andrea Campanella3a361452019-08-02 10:17:53 +0200219 NetconfControllerImpl.netconfIdleTimeout);
Sean Condon54d82432017-07-26 22:27:25 +0100220 log.info("Connecting to {} with timeouts C:{}, R:{}, I:{}", deviceInfo,
221 connectTimeout, replyTimeout, idleTimeout);
222
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700223 try {
224 startClient();
DongRyeol Chac29f9072018-11-06 14:05:56 +0900225 } catch (Exception e) {
226 stopClient();
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700227 throw new NetconfException("Failed to establish SSH with device " + deviceInfo, e);
228 }
229 }
230
231 private void startClient() throws IOException {
Yuta HIGUCHI2ee4fba2018-06-12 16:21:06 -0700232 log.info("Creating NETCONF session to {}",
Andrea Campanella3a361452019-08-02 10:17:53 +0200233 deviceInfo.getDeviceId());
Yuta HIGUCHI2ee4fba2018-06-12 16:21:06 -0700234
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700235 client = SshClient.setUpDefaultClient();
Andrea Campanella59227c32019-01-07 14:52:35 +0100236 if (idleTimeout != NetconfControllerImpl.netconfIdleTimeout) {
237 client.getProperties().putIfAbsent(FactoryManager.IDLE_TIMEOUT,
238 TimeUnit.SECONDS.toMillis(idleTimeout));
239 client.getProperties().putIfAbsent(FactoryManager.NIO2_READ_TIMEOUT,
240 TimeUnit.SECONDS.toMillis(idleTimeout + 15L));
241 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700242 client.start();
243 client.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
244 startSession();
DongRyeol Chac29f9072018-11-06 14:05:56 +0900245
246 disconnected = false;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700247 }
248
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700249 //TODO: Remove the default methods already implemented in NetconfSession
250
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700251 // FIXME blocking
252 @Deprecated
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700253 private void startSession() throws IOException {
254 final ConnectFuture connectFuture;
255 connectFuture = client.connect(deviceInfo.name(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200256 deviceInfo.ip().toString(),
257 deviceInfo.port())
Sean Condon54d82432017-07-26 22:27:25 +0100258 .verify(connectTimeout, TimeUnit.SECONDS);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700259 session = connectFuture.getSession();
Anurag Chadha8a9feb52020-07-28 18:35:05 +0530260 //Using the onos private ssh key at path SSH_KEY_PATH
261 if (deviceInfo.password().equals(EMPTY_STRING)) {
262 try (PEMParser pemParser = new PEMParser(new FileReader(SSH_KEY_PATH))) {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700263 JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
264 try {
265 KeyPair kp = converter.getKeyPair((PEMKeyPair) pemParser.readObject());
266 session.addPublicKeyIdentity(kp);
267 } catch (IOException e) {
Anurag Chadha8a9feb52020-07-28 18:35:05 +0530268 throw new NetconfException("Failed to authenticate session. Please check if ssk key is generated" +
269" on ONOS host machine at path " + SSH_KEY_PATH + " : ", e);
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700270 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700271 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700272 } else {
273 session.addPasswordIdentity(deviceInfo.password());
274 }
Sean Condon54d82432017-07-26 22:27:25 +0100275 session.auth().verify(connectTimeout, TimeUnit.SECONDS);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700276 Set<ClientSession.ClientSessionEvent> event = session.waitFor(
277 ImmutableSet.of(ClientSession.ClientSessionEvent.WAIT_AUTH,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200278 ClientSession.ClientSessionEvent.CLOSED,
279 ClientSession.ClientSessionEvent.AUTHED), 0);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700280
281 if (!event.contains(ClientSession.ClientSessionEvent.AUTHED)) {
282 log.debug("Session closed {} {}", event, session.isClosed());
283 throw new NetconfException("Failed to authenticate session with device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200284 deviceInfo + "check the user/pwd or key");
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700285 }
286 openChannel();
287 }
288
289 private PublicKey getPublicKey(byte[] keyBytes, String type)
290 throws NoSuchAlgorithmException, InvalidKeySpecException {
291
292 X509EncodedKeySpec spec =
293 new X509EncodedKeySpec(keyBytes);
294 KeyFactory kf = KeyFactory.getInstance(type);
295 return kf.generatePublic(spec);
296 }
297
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700298 // FIXME blocking
299 @Deprecated
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700300 private void openChannel() throws IOException {
301 channel = session.createSubsystemChannel("netconf");
302 OpenFuture channelFuture = channel.open();
Sean Condon54d82432017-07-26 22:27:25 +0100303 if (channelFuture.await(connectTimeout, TimeUnit.SECONDS)) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700304 if (channelFuture.isOpened()) {
305 streamHandler = new NetconfStreamThread(channel.getInvertedOut(), channel.getInvertedIn(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200306 channel.getInvertedErr(), deviceInfo,
307 new NetconfSessionDelegateImpl(), replies);
Andrea Campanella59227c32019-01-07 14:52:35 +0100308 primaryListeners.forEach(l -> streamHandler.addDeviceEventListener(l));
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700309 } else {
310 throw new NetconfException("Failed to open channel with device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200311 deviceInfo);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700312 }
313 sendHello();
314 }
315 }
316
317
318 @Beta
319 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
320 boolean openNewSession = false;
321 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
322 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
323 openNewSession = true;
324
325 } else if (subscriptionConnected &&
326 notificationFilterSchema != null &&
327 !Objects.equal(filterSchema, notificationFilterSchema)) {
328 // interleave supported and existing filter is NOT "no filtering"
329 // and was requested with different filtering schema
330 log.info("Cannot use existing session for subscription {} ({})",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200331 deviceInfo, filterSchema);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700332 openNewSession = true;
333 }
334
335 if (openNewSession) {
336 log.info("Creating notification session to {} with filter {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200337 deviceInfo, filterSchema);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700338 NetconfSession child = new NotificationSession(deviceInfo);
339
340 child.addDeviceOutputListener(new NotificationForwarder());
341
342 child.startSubscription(filterSchema);
343 children.add(child);
344 return;
345 }
346
347 // request to start interleaved notification session
348 String reply = sendRequest(createSubscriptionString(filterSchema));
349 if (!checkReply(reply)) {
350 throw new NetconfException("Subscription not successful with device "
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200351 + deviceInfo + " with reply " + reply);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700352 }
353 subscriptionConnected = true;
354 }
355
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700356 @Beta
357 @Override
358 public void startSubscription(String filterSchema) throws NetconfException {
359 if (!subscriptionConnected) {
360 notificationFilterSchema = filterSchema;
361 startSubscriptionStream(filterSchema);
362 }
363 streamHandler.setEnableNotifications(true);
364 }
365
366 @Beta
367 protected String createSubscriptionString(String filterSchema) {
368 StringBuilder subscriptionbuffer = new StringBuilder();
369 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
370 subscriptionbuffer.append(" <create-subscription\n");
371 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Laszlo Pappbdb64082018-09-11 12:21:29 +0100372 DriverService driverService = directory.get(DriverService.class);
373 Driver driver = driverService.getDriver(deviceInfo.getDeviceId());
374 if (driver != null) {
375 String stream = driver.getProperty(NOTIFICATION_STREAM);
376 if (stream != null) {
377 subscriptionbuffer.append(" <stream>");
378 subscriptionbuffer.append(stream);
379 subscriptionbuffer.append("</stream>\n");
380 }
381 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700382 // FIXME Only subtree filtering supported at the moment.
383 if (filterSchema != null) {
384 subscriptionbuffer.append(" ");
385 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
386 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
387 subscriptionbuffer.append(" ");
388 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
389 }
390 subscriptionbuffer.append(" </create-subscription>\n");
391 subscriptionbuffer.append("</rpc>\n");
392 subscriptionbuffer.append(ENDPATTERN);
393 return subscriptionbuffer.toString();
394 }
395
396 @Override
397 public void endSubscription() throws NetconfException {
398 if (subscriptionConnected) {
399 streamHandler.setEnableNotifications(false);
400 } else {
401 throw new NetconfException("Subscription does not exist.");
402 }
403 }
404
DongRyeol Chac29f9072018-11-06 14:05:56 +0900405 private void stopClient() {
406 if (session != null) {
407 try {
408 session.close();
409 } catch (IOException ex) {
410 log.warn("Cannot close session {} {}", sessionID, deviceInfo, ex);
411 }
412 }
413
414 if (channel != null) {
415 try {
416 channel.close();
417 } catch (IOException ex) {
418 log.warn("Cannot close channel {} {}", sessionID, deviceInfo, ex);
419 }
420 }
421
422 if (client != null) {
423 try {
424 client.close();
425 } catch (IOException ex) {
426 log.warn("Cannot close client {} {}", sessionID, deviceInfo, ex);
427 }
428
429 client.stop();
430 }
431 }
432
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700433 private void sendHello() throws NetconfException {
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700434 String serverHelloResponse = sendRequest(createHelloString(), true);
435 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponse);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700436 while (capabilityMatcher.find()) {
437 deviceCapabilities.add(capabilityMatcher.group(1));
438 }
439 sessionID = String.valueOf(-1);
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700440 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponse);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700441 if (sessionIDMatcher.find()) {
442 sessionID = sessionIDMatcher.group(1);
443 } else {
444 throw new NetconfException("Missing SessionID in server hello " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200445 "reponse.");
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700446 }
447
448 }
449
450 private String createHelloString() {
451 StringBuilder hellobuffer = new StringBuilder();
452 hellobuffer.append(XML_HEADER);
453 hellobuffer.append("\n");
454 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
455 hellobuffer.append(" <capabilities>\n");
456 onosCapabilities.forEach(
457 cap -> hellobuffer.append(" <capability>")
458 .append(cap)
459 .append("</capability>\n"));
460 hellobuffer.append(" </capabilities>\n");
461 hellobuffer.append("</hello>\n");
462 hellobuffer.append(ENDPATTERN);
463 return hellobuffer.toString();
464
465 }
466
467 @Override
468 public void checkAndReestablish() throws NetconfException {
DongRyeol Chac29f9072018-11-06 14:05:56 +0900469 if (disconnected) {
470 log.warn("Can't reopen connection for device because of disconnected {}", deviceInfo.getDeviceId());
471 throw new NetconfException("Can't reopen connection for device because of disconnected " + deviceInfo);
472 }
473
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700474 try {
zhongguo zhao98bb37a2018-08-28 16:17:06 +0800475 if (client.isClosed() || client.isClosing()) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700476 log.debug("Trying to restart the whole SSH connection with {}", deviceInfo.getDeviceId());
477 cleanUp();
478 startConnection();
zhongguo zhao98bb37a2018-08-28 16:17:06 +0800479 } else if (session.isClosed() || session.isClosing()) {
Kieran McPeakee1b418f2019-05-23 13:42:13 +0100480 log.debug("Trying to restart the session {} with {}", session, deviceInfo.getDeviceId());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700481 cleanUp();
482 startSession();
zhongguo zhao98bb37a2018-08-28 16:17:06 +0800483 } else if (channel.isClosed() || channel.isClosing()) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700484 log.debug("Trying to reopen the channel with {}", deviceInfo.getDeviceId());
485 cleanUp();
486 openChannel();
Andrea Campanella856f3132017-10-23 15:46:36 +0200487 } else {
488 return;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700489 }
490 if (subscriptionConnected) {
491 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
492 subscriptionConnected = false;
493 startSubscription(notificationFilterSchema);
494 }
Sean Condon7347de92017-07-21 12:17:25 +0100495 } catch (IOException | IllegalStateException e) {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700496 log.error("Can't reopen connection for device {}", e.getMessage());
497 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
498 }
499 }
500
501 private void cleanUp() {
502 //makes sure everything is at a clean state.
503 replies.clear();
David K. Bainbridge9b582b02019-02-01 16:04:05 -0800504 if (streamHandler != null) {
505 streamHandler.close();
506 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700507 }
508
509 @Override
510 public String requestSync(String request) throws NetconfException {
gyewan.an91d7e7e2019-01-17 15:12:48 +0900511 return requestSync(request, replyTimeout);
512 }
513
514 @Override
515 public String requestSync(String request, int timeout) throws NetconfException {
516 String reply = sendRequest(request, timeout);
Kim JeongWoo8b03bc52018-08-10 16:50:23 +0900517 if (!checkReply(reply)) {
518 throw new NetconfException("Request not successful with device "
Andrea Campanella3a361452019-08-02 10:17:53 +0200519 + deviceInfo + " with reply " + reply);
Kim JeongWoo8b03bc52018-08-10 16:50:23 +0900520 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700521 return reply;
522 }
523
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200524
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700525 // FIXME rename to align with what it actually do
Andrea Campanella3a361452019-08-02 10:17:53 +0200526
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200527 /**
528 * Validate and format netconf message.
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700529 * - NC1.0 if no EOM sequence present on {@code message}, append.
530 * - NC1.1 chunk-encode given message unless it already is chunk encoded
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200531 *
532 * @param message to format
533 * @return formated message
534 */
535 private String formatNetconfMessage(String message) {
536 if (deviceCapabilities.contains(NETCONF_11_CAPABILITY)) {
537 message = formatChunkedMessage(message);
538 } else {
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700539 if (!message.endsWith(ENDPATTERN)) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200540 message = message + NEW_LINE + ENDPATTERN;
541 }
542 }
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700543 return message;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200544 }
545
546 /**
547 * Validate and format message according to chunked framing mechanism.
548 *
549 * @param message to format
550 * @return formated message
551 */
552 private String formatChunkedMessage(String message) {
553 if (message.endsWith(ENDPATTERN)) {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700554 // message given had Netconf 1.0 EOM pattern -> remove
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200555 message = message.substring(0, message.length() - ENDPATTERN.length());
556 }
557 if (!message.startsWith(LF + HASH)) {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700558 // chunk encode message
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700559 message = LF + HASH + message.getBytes(UTF_8).length + LF + message + LF + HASH + HASH + LF;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200560 }
561 return message;
562 }
563
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700564 @Override
565 @Deprecated
566 public CompletableFuture<String> request(String request) {
567 return streamHandler.sendMessage(request);
568 }
569
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700570 /**
571 * {@inheritDoc}
572 * <p>
573 * FIXME Note: as of 1.12.0
574 * {@code request} must not include message-id, this method will assign
575 * and insert message-id on it's own.
576 * Will require ONOS-7019 to remove this limitation.
577 */
578 @Override
579 public CompletableFuture<String> rpc(String request) {
580
581 String rpc = request;
582 // - assign message-id
583 int msgId = messageIdInteger.incrementAndGet();
584 // - re-write request to insert message-id
585 // FIXME avoid using formatRequestMessageId
586 rpc = formatRequestMessageId(rpc, msgId);
587 // - ensure it contains XML header
588 rpc = formatXmlHeader(rpc);
589 // - use chunked framing if talking to NC 1.1 device
590 // FIXME avoid using formatNetconfMessage
591 rpc = formatNetconfMessage(rpc);
592
593 // TODO session liveness check & recovery
594
595 log.debug("Sending {} to {}", rpc, this.deviceInfo.getDeviceId());
596 return streamHandler.sendMessage(rpc, msgId)
Andrea Campanella3a361452019-08-02 10:17:53 +0200597 .handleAsync((reply, t) -> {
598 if (t != null) {
599 // secure transport-layer error
600 // cannot use NetconfException, which is
601 // checked Exception.
602 throw new NetconfTransportException(t);
603 } else {
604 // FIXME avoid using checkReply, error handling is weird
605 if (!checkReply(reply)) {
606 throw new NetconfTransportException("rpc-request not successful with device "
607 + deviceInfo + " with reply " + reply);
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700608 }
Andrea Campanella3a361452019-08-02 10:17:53 +0200609 return reply;
610 }
611 }, SharedExecutors.getPoolThreadExecutor());
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700612 }
613
Sean Condon54d82432017-07-26 22:27:25 +0100614 @Override
615 public int timeoutConnectSec() {
616 return connectTimeout;
617 }
618
619 @Override
620 public int timeoutReplySec() {
621 return replyTimeout;
622 }
623
624 @Override
625 public int timeoutIdleSec() {
626 return idleTimeout;
627 }
628
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700629 private CompletableFuture<String> request(String request, int messageId) {
630 return streamHandler.sendMessage(request, messageId);
631 }
632
gyewan.an91d7e7e2019-01-17 15:12:48 +0900633 private String sendRequest(String request, boolean isHello) throws NetconfException {
634 return sendRequest(request, isHello, replyTimeout);
635 }
636
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700637 private String sendRequest(String request) throws NetconfException {
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700638 // FIXME probably chunk-encoding too early
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200639 request = formatNetconfMessage(request);
gyewan.an91d7e7e2019-01-17 15:12:48 +0900640 return sendRequest(request, false, replyTimeout);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700641 }
642
gyewan.an91d7e7e2019-01-17 15:12:48 +0900643 private String sendRequest(String request, int timeout) throws NetconfException {
644 // FIXME probably chunk-encoding too early
645 request = formatNetconfMessage(request);
646 return sendRequest(request, false, timeout);
647 }
648
649 private String sendRequest(String request, boolean isHello, int timeout) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700650 checkAndReestablish();
651 int messageId = -1;
652 if (!isHello) {
653 messageId = messageIdInteger.getAndIncrement();
654 }
Yuta HIGUCHIb2d05242017-09-05 15:44:34 -0700655 // FIXME potentially re-writing chunked encoded String?
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700656 request = formatXmlHeader(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200657 request = formatRequestMessageId(request, messageId);
10068695e340ec92019-10-11 07:03:00 +0000658 int useTimeout = timeout > 0 ? timeout : replyTimeout;
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700659 log.debug("Sending request to NETCONF with timeout {} for {}",
10068695e340ec92019-10-11 07:03:00 +0000660 useTimeout, deviceInfo.name());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700661 CompletableFuture<String> futureReply = request(request, messageId);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700662 String rp;
663 try {
10068695e340ec92019-10-11 07:03:00 +0000664 rp = futureReply.get(useTimeout, TimeUnit.SECONDS);
Sean Condon7347de92017-07-21 12:17:25 +0100665 replies.remove(messageId); // Why here???
666 } catch (InterruptedException e) {
667 Thread.currentThread().interrupt();
668 throw new NetconfException("Interrupted waiting for reply for request" + request, e);
669 } catch (TimeoutException e) {
Sean Condon54d82432017-07-26 22:27:25 +0100670 throw new NetconfException("Timed out waiting for reply for request " +
10068695e340ec92019-10-11 07:03:00 +0000671 request + " after " + useTimeout + " sec.", e);
Sean Condon7347de92017-07-21 12:17:25 +0100672 } catch (ExecutionException e) {
673 log.warn("Closing session {} for {} due to unexpected Error", sessionID, deviceInfo, e);
DongRyeol Chac29f9072018-11-06 14:05:56 +0900674 stopClient();
Sean Condon7347de92017-07-21 12:17:25 +0100675 NetconfDeviceOutputEvent event = new NetconfDeviceOutputEvent(
676 NetconfDeviceOutputEvent.Type.SESSION_CLOSED,
677 null, "Closed due to unexpected error " + e.getCause(),
678 Optional.of(-1), deviceInfo);
679 publishEvent(event);
680 errorReplies.clear(); // move to cleanUp()?
681 cleanUp();
682
683 throw new NetconfException("Closing session " + sessionID + " for " + deviceInfo +
684 " for request " + request, e);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700685 }
686 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
687 return rp.trim();
688 }
689
690 private String formatRequestMessageId(String request, int messageId) {
691 if (request.contains(MESSAGE_ID_STRING)) {
692 //FIXME if application provides his own counting of messages this fails that count
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700693 // FIXME assumes message-id is integer. RFC6241 allows anything as long as it is allowed in XML
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700694 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200695 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700696 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
697 //FIXME find out a better way to enforce the presence of message-id
698 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
699 + messageId + "\"" + ">");
700 }
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700701 request = updateRequestLength(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200702 return request;
703 }
704
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700705 private String updateRequestLength(String request) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200706 if (request.contains(LF + HASH + HASH + LF)) {
707 int oldLen = Integer.parseInt(request.split(HASH)[1].split(LF)[0]);
708 String rpcWithEnding = request.substring(request.indexOf('<'));
709 String firstBlock = request.split(MSGLEN_REGEX_PATTERN)[1].split(LF + HASH + HASH + LF)[0];
710 int newLen = 0;
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700711 newLen = firstBlock.getBytes(UTF_8).length;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200712 if (oldLen != newLen) {
713 return LF + HASH + newLen + LF + rpcWithEnding;
714 }
715 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700716 return request;
717 }
718
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700719 /**
720 * Ensures xml start directive/declaration appears in the {@code request}.
Andrea Campanella3a361452019-08-02 10:17:53 +0200721 *
Yuta HIGUCHI371667d2017-09-05 17:30:51 -0700722 * @param request RPC request message
723 * @return XML RPC message
724 */
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700725 private String formatXmlHeader(String request) {
Sean Condon2d647172017-09-19 12:29:13 +0100726 if (!request.contains(XML_HEADER)) {
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700727 //FIXME if application provides his own XML header of different type there is a clash
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200728 if (request.startsWith(LF + HASH)) {
729 request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
730 } else {
731 request = XML_HEADER + "\n" + request;
732 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700733 }
734 return request;
735 }
736
737 @Override
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700738 public String getSessionId() {
739 return sessionID;
740 }
741
742 @Override
743 public Set<String> getDeviceCapabilitiesSet() {
744 return Collections.unmodifiableSet(deviceCapabilities);
745 }
746
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700747 @Override
748 public void setOnosCapabilities(Iterable<String> capabilities) {
749 onosCapabilities = capabilities;
750 }
751
752
753 @Override
754 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
755 streamHandler.addDeviceEventListener(listener);
756 primaryListeners.add(listener);
757 }
758
759 @Override
760 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
761 primaryListeners.remove(listener);
762 streamHandler.removeDeviceEventListener(listener);
763 }
764
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700765 @Override
766 protected boolean checkReply(String reply) {
767 // Overridden to record error logs
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700768 if (reply != null) {
769 if (!reply.contains("<rpc-error>")) {
770 log.debug("Device {} sent reply {}", deviceInfo, reply);
771 return true;
772 } else if (reply.contains("<ok/>")
773 || (reply.contains("<rpc-error>")
774 && reply.contains("warning"))) {
Yuta HIGUCHI6e6c26e2017-09-06 14:25:57 -0700775 // FIXME rpc-error with a warning is considered same as Ok??
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700776 log.debug("Device {} sent reply {}", deviceInfo, reply);
777 return true;
778 }
779 }
780 log.warn("Device {} has error in reply {}", deviceInfo, reply);
781 return false;
782 }
783
zhongguo zhao78eab372018-08-27 16:22:39 +0800784 @Override
785 public boolean close() throws NetconfException {
786 try {
DongRyeol Chac29f9072018-11-06 14:05:56 +0900787 if (client != null && (client.isClosed() || client.isClosing())) {
788 return true;
789 }
790
zhongguo zhao78eab372018-08-27 16:22:39 +0800791 return super.close();
792 } catch (IOException ioe) {
793 throw new NetconfException(ioe.getMessage());
794 } finally {
DongRyeol Chac29f9072018-11-06 14:05:56 +0900795 disconnected = true;
796 stopClient();
zhongguo zhao78eab372018-08-27 16:22:39 +0800797 }
798 }
799
Sean Condon7347de92017-07-21 12:17:25 +0100800 protected void publishEvent(NetconfDeviceOutputEvent event) {
801 primaryListeners.forEach(lsnr -> {
802 if (lsnr.isRelevant(event)) {
803 lsnr.event(event);
804 }
805 });
806 }
807
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700808 static class NotificationSession extends NetconfSessionMinaImpl {
809
810 private String notificationFilter;
811
812 NotificationSession(NetconfDeviceInfo deviceInfo)
813 throws NetconfException {
814 super(deviceInfo);
815 }
816
817 @Override
818 protected void startSubscriptionStream(String filterSchema)
819 throws NetconfException {
820
821 notificationFilter = filterSchema;
822 requestSync(createSubscriptionString(filterSchema));
823 }
824
825 @Override
826 public String toString() {
827 return MoreObjects.toStringHelper(getClass())
828 .add("deviceInfo", deviceInfo)
829 .add("sessionID", getSessionId())
830 .add("notificationFilter", notificationFilter)
831 .toString();
832 }
833 }
834
835 /**
836 * Listener attached to child session for notification streaming.
837 * <p>
838 * Forwards all notification event from child session to primary session
839 * listeners.
840 */
841 private final class NotificationForwarder
842 implements NetconfDeviceOutputEventListener {
843
844 @Override
845 public boolean isRelevant(NetconfDeviceOutputEvent event) {
846 return event.type() == Type.DEVICE_NOTIFICATION;
847 }
848
849 @Override
850 public void event(NetconfDeviceOutputEvent event) {
Sean Condon7347de92017-07-21 12:17:25 +0100851 publishEvent(event);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700852 }
853 }
854
855 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
856
857 @Override
858 public void notify(NetconfDeviceOutputEvent event) {
859 Optional<Integer> messageId = event.getMessageID();
860 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200861 replies.keySet());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700862 if (!messageId.isPresent()) {
863 errorReplies.add(event.getMessagePayload());
864 log.error("Device {} sent error reply {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200865 event.getDeviceInfo(), event.getMessagePayload());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700866 return;
867 }
David K. Bainbridge77d8ca42019-02-01 11:47:29 -0800868 // Remove the message as it has been processed.
869 CompletableFuture<String> completedReply = replies.remove(messageId.get());
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700870 if (completedReply != null) {
871 completedReply.complete(event.getMessagePayload());
872 }
873 }
874 }
875
Yuta HIGUCHI2ee4fba2018-06-12 16:21:06 -0700876 /**
877 * @deprecated in 1.14.0
878 */
879 @Deprecated
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700880 public static class MinaSshNetconfSessionFactory implements NetconfSessionFactory {
881
882 @Override
gyewan.an91d7e7e2019-01-17 15:12:48 +0900883 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo,
884 NetconfController netconfController) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700885 return new NetconfSessionMinaImpl(netconfDeviceInfo);
886 }
887 }
Sean Condon54d82432017-07-26 22:27:25 +0100888}