blob: 8303868e5f7038411d32f670dc6420bb82552c49 [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
andreaeb70a942015-10-16 21:34:46 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070017package org.onosproject.netconf.ctl.impl;
andreaeb70a942015-10-16 21:34:46 -070018
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090019import com.google.common.annotations.Beta;
andreaeb70a942015-10-16 21:34:46 -070020import ch.ethz.ssh2.Connection;
21import ch.ethz.ssh2.Session;
Andrea Campanellac3627842017-04-04 18:06:54 +020022import ch.ethz.ssh2.channel.Channel;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070023
24import com.google.common.base.MoreObjects;
25import com.google.common.base.Objects;
andreaeb70a942015-10-16 21:34:46 -070026import com.google.common.base.Preconditions;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070027import org.onosproject.netconf.NetconfSessionFactory;
Yuta HIGUCHI89111d92017-05-04 11:29:17 -070028import org.onosproject.netconf.DatastoreId;
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070029import org.onosproject.netconf.FilteringNetconfDeviceOutputEventListener;
andreaeb70a942015-10-16 21:34:46 -070030import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080031import org.onosproject.netconf.NetconfDeviceOutputEvent;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070032import org.onosproject.netconf.NetconfDeviceOutputEvent.Type;
Andrea Campanella101417d2015-12-11 17:58:07 -080033import org.onosproject.netconf.NetconfDeviceOutputEventListener;
34import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070035import org.onosproject.netconf.NetconfSession;
36import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
andreaeb70a942015-10-16 21:34:46 -070039import java.io.IOException;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020040import java.util.ArrayList;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070041import java.util.Collection;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080042import java.util.Collections;
Aaron Kruglikov72db6422017-02-13 12:16:51 -080043import java.util.LinkedHashSet;
andreaeb70a942015-10-16 21:34:46 -070044import java.util.List;
Andrea Campanella101417d2015-12-11 17:58:07 -080045import java.util.Map;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020046import java.util.Optional;
Aaron Kruglikov72db6422017-02-13 12:16:51 -080047import java.util.Set;
Andrea Campanella101417d2015-12-11 17:58:07 -080048import java.util.concurrent.CompletableFuture;
Sean Condond2c8d472017-02-17 17:09:39 +000049import java.util.concurrent.ConcurrentHashMap;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070050import java.util.concurrent.CopyOnWriteArrayList;
Andrea Campanellab029b9e2016-01-29 11:05:36 -080051import java.util.concurrent.ExecutionException;
52import java.util.concurrent.TimeUnit;
53import java.util.concurrent.TimeoutException;
Andrea Campanella101417d2015-12-11 17:58:07 -080054import java.util.concurrent.atomic.AtomicInteger;
Sean Condond2c8d472017-02-17 17:09:39 +000055
Aaron Kruglikov72db6422017-02-13 12:16:51 -080056import java.util.regex.Pattern;
57import java.util.regex.Matcher;
Andrea Campanella101417d2015-12-11 17:58:07 -080058
andreaeb70a942015-10-16 21:34:46 -070059/**
60 * Implementation of a NETCONF session to talk to a device.
61 */
62public class NetconfSessionImpl implements NetconfSession {
63
Andrea Campanella101417d2015-12-11 17:58:07 -080064 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070065 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080066
Andrea Campanella101417d2015-12-11 17:58:07 -080067 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080068 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080069 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080070 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080071 private static final String END_OF_RPC_OPEN_TAG = "\">";
72 private static final String EQUAL = "=";
73 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090074 private static final String RPC_OPEN = "<rpc ";
75 private static final String RPC_CLOSE = "</rpc>";
76 private static final String GET_OPEN = "<get>";
77 private static final String GET_CLOSE = "</get>";
78 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
79 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090080 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
81 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090082 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
83 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090084 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
85 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
86 private static final String TARGET_OPEN = "<target>";
87 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000088 private static final String CONFIG_OPEN = "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090089 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080090 private static final String XML_HEADER =
91 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090092 private static final String NETCONF_BASE_NAMESPACE =
93 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
94 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
95 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090096 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
97 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -070098
Aaron Kruglikov72db6422017-02-13 12:16:51 -080099 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
Sean Condond2c8d472017-02-17 17:09:39 +0000100
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800101 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
102 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
103
104 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
105 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
106
107 private String sessionID;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700108 private final AtomicInteger messageIdInteger = new AtomicInteger(1);
andreaeb70a942015-10-16 21:34:46 -0700109 private Connection netconfConnection;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700110 protected final NetconfDeviceInfo deviceInfo;
andreaeb70a942015-10-16 21:34:46 -0700111 private Session sshSession;
112 private boolean connectionActive;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800113 private Iterable<String> onosCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800114 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800115
116 /* NOTE: the "serverHelloResponseOld" is deprecated in 1.10.0 and should eventually be removed */
117 @Deprecated
118 private String serverHelloResponseOld;
119 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
helenyrwu0407c642016-06-09 12:01:30 -0700120 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -0800121 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200122 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -0700123 private boolean subscriptionConnected = false;
Andrea Campanellac3627842017-04-04 18:06:54 +0200124 private String notificationFilterSchema = null;
andreaeb70a942015-10-16 21:34:46 -0700125
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700126 private final Collection<NetconfDeviceOutputEventListener> primaryListeners =
127 new CopyOnWriteArrayList<>();
128 private final Collection<NetconfSession> children =
129 new CopyOnWriteArrayList<>();
130
andreaeb70a942015-10-16 21:34:46 -0700131
Andrea Campanella101417d2015-12-11 17:58:07 -0800132 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700133 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900134 this.netconfConnection = null;
135 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700136 connectionActive = false;
Sean Condond2c8d472017-02-17 17:09:39 +0000137 replies = new ConcurrentHashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200138 errorReplies = new ArrayList<>();
andreaeb70a942015-10-16 21:34:46 -0700139 startConnection();
140 }
141
Andrea Campanella101417d2015-12-11 17:58:07 -0800142 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700143 if (!connectionActive) {
144 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000145 int connectTimeout = NetconfControllerImpl.netconfConnectTimeout;
146
Andrea Campanella101417d2015-12-11 17:58:07 -0800147 try {
Sean Condon334ad692016-12-13 17:56:56 +0000148 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800149 } catch (IOException e) {
Yuta HIGUCHI0184a7b2017-03-31 13:13:58 -0700150 throw new NetconfException("Cannot open a connection with device " + deviceInfo, e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800151 }
andreaeb70a942015-10-16 21:34:46 -0700152 boolean isAuthenticated;
153 try {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800154 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
155 log.debug("Authenticating with key file to device {} with username {}",
156 deviceInfo.getDeviceId(), deviceInfo.name());
157 isAuthenticated = netconfConnection.authenticateWithPublicKey(
158 deviceInfo.name(), deviceInfo.getKeyFile(),
159 deviceInfo.password().equals("") ? null : deviceInfo.password());
160 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600161 log.debug("Authenticating with key to device {} with username {}",
Andrea Campanellac3627842017-04-04 18:06:54 +0200162 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700163 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600164 deviceInfo.name(), deviceInfo.getKey(),
165 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700166 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600167 log.debug("Authenticating to device {} with username {} with password",
Andrea Campanella50d25212016-02-26 13:06:23 -0800168 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700169 isAuthenticated = netconfConnection.authenticateWithPassword(
170 deviceInfo.name(), deviceInfo.password());
171 }
172 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500173 log.error("Authentication connection to device {} failed",
174 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800175 throw new NetconfException("Authentication connection to device " +
176 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700177 }
178
179 connectionActive = true;
180 Preconditions.checkArgument(isAuthenticated,
Andrea Campanellad264b492016-03-01 09:46:06 -0800181 "Authentication to device %s with username " +
182 "%s failed",
Andrea Campanella50d25212016-02-26 13:06:23 -0800183 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700184 startSshSession();
185 }
186 }
187
Andrea Campanella101417d2015-12-11 17:58:07 -0800188 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700189 try {
190 sshSession = netconfConnection.openSession();
191 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700192 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
193 sshSession.getStderr(), deviceInfo,
Sean Condond2c8d472017-02-17 17:09:39 +0000194 new NetconfSessionDelegateImpl(),
195 replies);
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700196 this.addDeviceOutputListener(new FilteringNetconfDeviceOutputEventListener(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700197 sendHello();
198 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200199 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800200 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
201 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700202 }
203 }
204
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900205
206 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700207 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
208 boolean openNewSession = false;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800209 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700210 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
211 openNewSession = true;
212
213 } else if (subscriptionConnected &&
214 notificationFilterSchema != null &&
215 !Objects.equal(filterSchema, notificationFilterSchema)) {
216 // interleave supported and existing filter is NOT "no filtering"
217 // and was requested with different filtering schema
218 log.info("Cannot use existing session for subscription {} ({})",
219 deviceInfo, filterSchema);
220 openNewSession = true;
helenyrwu0407c642016-06-09 12:01:30 -0700221 }
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700222
223 if (openNewSession) {
224 log.info("Creating notification session to {} with filter {}",
225 deviceInfo, filterSchema);
226 NetconfSession child = new NotificationSession(deviceInfo);
227
228 child.addDeviceOutputListener(new NotificationForwarder());
229
230 child.startSubscription(filterSchema);
231 children.add(child);
232 return;
233 }
234
235 // request to start interleaved notification session
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900236 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700237 if (!checkReply(reply)) {
238 throw new NetconfException("Subscription not successful with device "
239 + deviceInfo + " with reply " + reply);
240 }
241 subscriptionConnected = true;
242 }
243
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900244 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700245 public void startSubscription() throws NetconfException {
246 if (!subscriptionConnected) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700247 startSubscriptionStream(null);
helenyrwu0407c642016-06-09 12:01:30 -0700248 }
249 streamHandler.setEnableNotifications(true);
250 }
251
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900252 @Beta
253 @Override
254 public void startSubscription(String filterSchema) throws NetconfException {
255 if (!subscriptionConnected) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200256 notificationFilterSchema = filterSchema;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700257 startSubscriptionStream(filterSchema);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900258 }
259 streamHandler.setEnableNotifications(true);
260 }
261
262 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700263 protected String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700264 StringBuilder subscriptionbuffer = new StringBuilder();
265 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
266 subscriptionbuffer.append(" <create-subscription\n");
267 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900268 // FIXME Only subtree filtering supported at the moment.
269 if (filterSchema != null) {
270 subscriptionbuffer.append(" ");
271 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
272 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
273 subscriptionbuffer.append(" ");
274 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
275 }
helenyrwu0407c642016-06-09 12:01:30 -0700276 subscriptionbuffer.append(" </create-subscription>\n");
277 subscriptionbuffer.append("</rpc>\n");
278 subscriptionbuffer.append(ENDPATTERN);
279 return subscriptionbuffer.toString();
280 }
281
282 @Override
283 public void endSubscription() throws NetconfException {
284 if (subscriptionConnected) {
285 streamHandler.setEnableNotifications(false);
286 } else {
287 throw new NetconfException("Subscription does not exist.");
288 }
289 }
290
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800291 private void sendHello() throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700292 serverHelloResponseOld = sendRequest(createHelloString(), true);
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800293 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
294 while (capabilityMatcher.find()) {
295 deviceCapabilities.add(capabilityMatcher.group(1));
296 }
297 sessionID = String.valueOf(-1);
298 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
299 if (sessionIDMatcher.find()) {
300 sessionID = sessionIDMatcher.group(1);
301 } else {
302 throw new NetconfException("Missing SessionID in server hello " +
303 "reponse.");
304 }
305
andreaeb70a942015-10-16 21:34:46 -0700306 }
307
308 private String createHelloString() {
309 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800310 hellobuffer.append(XML_HEADER);
311 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700312 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
313 hellobuffer.append(" <capabilities>\n");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800314 onosCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800315 cap -> hellobuffer.append(" <capability>")
316 .append(cap)
317 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700318 hellobuffer.append(" </capabilities>\n");
319 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800320 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700321 return hellobuffer.toString();
322
323 }
324
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700325 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200326 public void checkAndReestablish() throws NetconfException {
327 if (sshSession.getState() != Channel.STATE_OPEN) {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800328 try {
Andrea Campanellac3627842017-04-04 18:06:54 +0200329 log.debug("Trying to reopen the Sesion with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800330 startSshSession();
Andrea Campanellac3627842017-04-04 18:06:54 +0200331 } catch (IOException | IllegalStateException e) {
332 log.debug("Trying to reopen the Connection with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800333 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200334 connectionActive = false;
335 replies.clear();
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800336 startConnection();
Andrea Campanellac3627842017-04-04 18:06:54 +0200337 if (subscriptionConnected) {
338 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
339 subscriptionConnected = false;
340 startSubscription(notificationFilterSchema);
341 }
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800342 } catch (IOException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200343 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800344 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800345 }
346 }
347 }
348 }
349
andreaeb70a942015-10-16 21:34:46 -0700350 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800351 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800352 if (!request.contains(ENDPATTERN)) {
353 request = request + NEW_LINE + ENDPATTERN;
354 }
355 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300356 checkReply(reply);
357 return reply;
andreaeb70a942015-10-16 21:34:46 -0700358 }
359
360 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000361 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800362 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000363 return streamHandler.sendMessage(request);
364 }
365
366 private CompletableFuture<String> request(String request, int messageId) {
367 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800368 }
369
370 private String sendRequest(String request) throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700371 return sendRequest(request, false);
372 }
373
374 private String sendRequest(String request, boolean isHello) throws NetconfException {
Andrea Campanellac3627842017-04-04 18:06:54 +0200375 checkAndReestablish();
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700376 int messageId = -1;
377 if (!isHello) {
378 messageId = messageIdInteger.getAndIncrement();
379 }
Sean Condond2c8d472017-02-17 17:09:39 +0000380 request = formatRequestMessageId(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800381 request = formatXmlHeader(request);
Sean Condond2c8d472017-02-17 17:09:39 +0000382 CompletableFuture<String> futureReply = request(request, messageId);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300383 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800384 String rp;
385 try {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300386 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000387 replies.remove(messageId);
Sean Condon7347de92017-07-21 12:17:25 +0100388 } catch (InterruptedException e) {
389 Thread.currentThread().interrupt();
390 throw new NetconfException("Interrupted waiting for reply for request" + request, e);
391 } catch (TimeoutException e) {
392 throw new NetconfException("Timed out waiting for reply for request " + request, e);
393 } catch (ExecutionException e) {
394 log.warn("Closing session {} for {} due to unexpected Error", sessionID, deviceInfo, e);
395
396 netconfConnection.close(); //Closes the socket which should interrupt NetconfStreamThread
397 sshSession.close();
398
399 NetconfDeviceOutputEvent event = new NetconfDeviceOutputEvent(
400 NetconfDeviceOutputEvent.Type.SESSION_CLOSED,
401 null, "Closed due to unexpected error " + e.getCause(),
402 Optional.of(-1), deviceInfo);
403 publishEvent(event);
404 replies.clear();
405 errorReplies.clear();
406
407 throw new NetconfException("Closing session " + sessionID + " for " + deviceInfo +
408 " for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800409 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800410 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800411 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800412 }
413
Sean Condond2c8d472017-02-17 17:09:39 +0000414 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800415 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000416 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800417 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Sean Condond2c8d472017-02-17 17:09:39 +0000418 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800419 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
420 //FIXME find out a better way to enforce the presence of message-id
421 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000422 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800423 }
424 return request;
425 }
426
427 private String formatXmlHeader(String request) {
428 if (!request.contains(XML_HEADER)) {
429 //FIXME if application provieds his own XML header of different type there is a clash
430 request = XML_HEADER + "\n" + request;
431 }
432 return request;
433 }
434
Andrea Campanella101417d2015-12-11 17:58:07 -0800435 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900436 public String doWrappedRpc(String request) throws NetconfException {
437 StringBuilder rpc = new StringBuilder(XML_HEADER);
438 rpc.append(RPC_OPEN);
439 rpc.append(MESSAGE_ID_STRING);
440 rpc.append(EQUAL);
441 rpc.append("\"");
442 rpc.append(messageIdInteger.get());
443 rpc.append("\" ");
444 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
445 rpc.append(request);
446 rpc.append(RPC_CLOSE).append(NEW_LINE);
447 rpc.append(ENDPATTERN);
448 String reply = sendRequest(rpc.toString());
449 checkReply(reply);
450 return reply;
451 }
452
453 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800454 public String get(String request) throws NetconfException {
455 return requestSync(request);
456 }
457
458 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900459 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
460 StringBuilder rpc = new StringBuilder(XML_HEADER);
461 rpc.append(RPC_OPEN);
462 rpc.append(MESSAGE_ID_STRING);
463 rpc.append(EQUAL);
464 rpc.append("\"");
465 rpc.append(messageIdInteger.get());
466 rpc.append("\" ");
467 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
468 rpc.append(GET_OPEN).append(NEW_LINE);
469 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900470 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900471 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900472 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900473 }
474 if (withDefaultsMode != null) {
475 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
476 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
477 }
478 rpc.append(GET_CLOSE).append(NEW_LINE);
479 rpc.append(RPC_CLOSE).append(NEW_LINE);
480 rpc.append(ENDPATTERN);
481 String reply = sendRequest(rpc.toString());
482 checkReply(reply);
483 return reply;
484 }
485
486 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700487 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300488 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700489 }
490
491 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700492 public String getConfig(DatastoreId netconfTargetConfig,
493 String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800494 StringBuilder rpc = new StringBuilder(XML_HEADER);
495 rpc.append("<rpc ");
496 rpc.append(MESSAGE_ID_STRING);
497 rpc.append(EQUAL);
498 rpc.append("\"");
499 rpc.append(messageIdInteger.get());
500 rpc.append("\" ");
501 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700502 rpc.append("<get-config>\n");
503 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300504 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700505 rpc.append("</source>");
506 if (configurationSchema != null) {
507 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800508 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700509 rpc.append("</filter>\n");
510 }
511 rpc.append("</get-config>\n");
512 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800513 rpc.append(ENDPATTERN);
514 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800515 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700516 }
517
518 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800519 public boolean editConfig(String newConfiguration) throws NetconfException {
520 newConfiguration = newConfiguration + ENDPATTERN;
521 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700522 }
523
524 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700525 public boolean editConfig(DatastoreId netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800526 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800527 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800528 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900529 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800530 rpc.append(MESSAGE_ID_STRING);
531 rpc.append(EQUAL);
532 rpc.append("\"");
533 rpc.append(messageIdInteger.get());
534 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900535 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
536 rpc.append(EDIT_CONFIG_OPEN).append("\n");
537 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300538 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900539 rpc.append(TARGET_CLOSE).append("\n");
540 if (mode != null) {
541 rpc.append(DEFAULT_OPERATION_OPEN);
542 rpc.append(mode);
543 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
544 }
545 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800546 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900547 rpc.append(CONFIG_CLOSE).append("\n");
548 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
549 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800550 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500551 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900552 String reply = sendRequest(rpc.toString());
553 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800554 }
555
556 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700557 public boolean copyConfig(DatastoreId destination,
558 DatastoreId source)
559 throws NetconfException {
560 return bareCopyConfig(destination.asXml(), source.asXml());
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300561 }
562
563 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700564 public boolean copyConfig(DatastoreId netconfTargetConfig,
565 String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800566 throws NetconfException {
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700567 return bareCopyConfig(netconfTargetConfig.asXml(),
568 normalizeCopyConfigParam(newConfiguration));
569 }
570
571 @Override
572 public boolean copyConfig(String netconfTargetConfig,
573 String newConfiguration) throws NetconfException {
574 return bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
575 normalizeCopyConfigParam(newConfiguration));
576 }
577
578 /**
579 * Normalize String parameter passed to copy-config API.
580 * <p>
581 * Provided for backward compatibility purpose
582 *
583 * @param input passed to copyConfig API
584 * @return XML likely to be suitable for copy-config source or target
585 */
586 private static CharSequence normalizeCopyConfigParam(String input) {
587 input = input.trim();
588 if (input.startsWith("<url")) {
589 return input;
590 } else if (!input.startsWith("<")) {
591 // assume it is a datastore name
592 return DatastoreId.datastore(input).asXml();
593 } else if (!input.startsWith("<config>")) {
594 return "<config>" + input + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700595 }
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700596 return input;
597 }
598
599 private boolean bareCopyConfig(CharSequence target,
600 CharSequence source)
601 throws NetconfException {
602
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800603 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400604 rpc.append(RPC_OPEN);
605 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700606 rpc.append("<copy-config>");
607 rpc.append("<target>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700608 rpc.append(target);
andreaeb70a942015-10-16 21:34:46 -0700609 rpc.append("</target>");
610 rpc.append("<source>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700611 rpc.append(source);
andreaeb70a942015-10-16 21:34:46 -0700612 rpc.append("</source>");
613 rpc.append("</copy-config>");
614 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800615 rpc.append(ENDPATTERN);
616 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700617 }
618
619 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700620 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
621 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
andreaeb70a942015-10-16 21:34:46 -0700622 log.warn("Target configuration for delete operation can't be \"running\"",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300623 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700624 return false;
625 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800626 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700627 rpc.append("<rpc>");
628 rpc.append("<delete-config>");
629 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300630 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700631 rpc.append("</target>");
632 rpc.append("</delete-config>");
633 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800634 rpc.append(ENDPATTERN);
635 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700636 }
637
638 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700639 public boolean lock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800640 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700641 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700642 rpc.append("<lock>");
643 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700644 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700645 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700646 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700647 rpc.append("</target>");
648 rpc.append("</lock>");
649 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800650 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700651 String lockReply = sendRequest(rpc.toString());
652 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700653 }
654
655 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700656 public boolean unlock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800657 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700658 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700659 rpc.append("<unlock>");
660 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700661 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700662 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700663 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700664 rpc.append("</target>");
665 rpc.append("</unlock>");
666 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800667 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700668 String unlockReply = sendRequest(rpc.toString());
669 return checkReply(unlockReply);
670 }
671
672 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800673 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700674 return close(false);
675 }
676
Andrea Campanella101417d2015-12-11 17:58:07 -0800677 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700678 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700679 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700680 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700681 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700682 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700683 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700684 }
andreaeb70a942015-10-16 21:34:46 -0700685 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800686 rpc.append(ENDPATTERN);
687 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700688 }
689
690 @Override
691 public String getSessionId() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800692 return sessionID;
andreaeb70a942015-10-16 21:34:46 -0700693 }
694
695 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800696 public Set<String> getDeviceCapabilitiesSet() {
697 return Collections.unmodifiableSet(deviceCapabilities);
698 }
699
700 @Deprecated
701 @Override
andreaeb70a942015-10-16 21:34:46 -0700702 public String getServerCapabilities() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800703 return serverHelloResponseOld;
704 }
705
706 @Deprecated
707 @Override
708 public void setDeviceCapabilities(List<String> capabilities) {
709 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700710 }
711
712 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800713 public void setOnosCapabilities(Iterable<String> capabilities) {
714 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700715 }
716
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800717
Andrea Campanella101417d2015-12-11 17:58:07 -0800718 @Override
719 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700720 streamHandler.addDeviceEventListener(listener);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700721 primaryListeners.add(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800722 }
723
724 @Override
725 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700726 primaryListeners.remove(listener);
helenyrwu0407c642016-06-09 12:01:30 -0700727 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800728 }
729
730 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700731 if (reply != null) {
732 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900733 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700734 return true;
735 } else if (reply.contains("<ok/>")
736 || (reply.contains("<rpc-error>")
737 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900738 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700739 return true;
740 }
741 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800742 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700743 return false;
744 }
745
Sean Condon7347de92017-07-21 12:17:25 +0100746 protected void publishEvent(NetconfDeviceOutputEvent event) {
747 primaryListeners.forEach(lsnr -> {
748 if (lsnr.isRelevant(event)) {
749 lsnr.event(event);
750 }
751 });
752 }
753
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700754 static class NotificationSession extends NetconfSessionImpl {
755
756 private String notificationFilter;
757
758 NotificationSession(NetconfDeviceInfo deviceInfo)
759 throws NetconfException {
760 super(deviceInfo);
761 }
762
763 @Override
764 protected void startSubscriptionStream(String filterSchema)
765 throws NetconfException {
766
767 notificationFilter = filterSchema;
768 requestSync(createSubscriptionString(filterSchema));
769 }
770
771 @Override
772 public String toString() {
773 return MoreObjects.toStringHelper(getClass())
774 .add("deviceInfo", deviceInfo)
775 .add("sessionID", getSessionId())
776 .add("notificationFilter", notificationFilter)
777 .toString();
778 }
779 }
780
781 /**
782 * Listener attached to child session for notification streaming.
783 *
784 * Forwards all notification event from child session to primary session
785 * listeners.
786 */
787 private final class NotificationForwarder
788 implements NetconfDeviceOutputEventListener {
789
790 @Override
791 public boolean isRelevant(NetconfDeviceOutputEvent event) {
792 return event.type() == Type.DEVICE_NOTIFICATION;
793 }
794
795 @Override
796 public void event(NetconfDeviceOutputEvent event) {
Sean Condon7347de92017-07-21 12:17:25 +0100797 publishEvent(event);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700798 }
799 }
800
Andrea Campanella101417d2015-12-11 17:58:07 -0800801 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700802
Andrea Campanella101417d2015-12-11 17:58:07 -0800803 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200804 public void notify(NetconfDeviceOutputEvent event) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200805 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200806 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
807 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200808 if (!messageId.isPresent()) {
809 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800810 log.error("Device {} sent error reply {}",
811 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200812 return;
813 }
814 CompletableFuture<String> completedReply =
815 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900816 if (completedReply != null) {
817 completedReply.complete(event.getMessagePayload());
818 }
andreaeb70a942015-10-16 21:34:46 -0700819 }
820 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700821
822 public static class SshNetconfSessionFactory implements NetconfSessionFactory {
823
824 @Override
825 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
826 return new NetconfSessionImpl(netconfDeviceInfo);
827 }
828 }
andreaeb70a942015-10-16 21:34:46 -0700829}