blob: 48727a273a4ae5bd417576906459896af6a472be [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
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;
Kamil Stasiak9f59f442017-05-02 11:02:24 +020027import com.google.common.collect.ImmutableList;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070028import org.onosproject.netconf.NetconfSessionFactory;
Yuta HIGUCHI89111d92017-05-04 11:29:17 -070029import org.onosproject.netconf.DatastoreId;
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070030import org.onosproject.netconf.FilteringNetconfDeviceOutputEventListener;
andreaeb70a942015-10-16 21:34:46 -070031import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080032import org.onosproject.netconf.NetconfDeviceOutputEvent;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070033import org.onosproject.netconf.NetconfDeviceOutputEvent.Type;
Andrea Campanella101417d2015-12-11 17:58:07 -080034import org.onosproject.netconf.NetconfDeviceOutputEventListener;
35import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070036import org.onosproject.netconf.NetconfSession;
37import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39
Yuta HIGUCHI15677982017-08-16 15:50:29 -070040import static java.nio.charset.StandardCharsets.UTF_8;
41
andreaeb70a942015-10-16 21:34:46 -070042import java.io.IOException;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020043import java.util.ArrayList;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070044import java.util.Collection;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080045import java.util.Collections;
Aaron Kruglikov72db6422017-02-13 12:16:51 -080046import java.util.LinkedHashSet;
andreaeb70a942015-10-16 21:34:46 -070047import java.util.List;
Andrea Campanella101417d2015-12-11 17:58:07 -080048import java.util.Map;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020049import java.util.Optional;
Aaron Kruglikov72db6422017-02-13 12:16:51 -080050import java.util.Set;
Andrea Campanella101417d2015-12-11 17:58:07 -080051import java.util.concurrent.CompletableFuture;
Sean Condond2c8d472017-02-17 17:09:39 +000052import java.util.concurrent.ConcurrentHashMap;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -070053import java.util.concurrent.CopyOnWriteArrayList;
Andrea Campanellab029b9e2016-01-29 11:05:36 -080054import java.util.concurrent.ExecutionException;
55import java.util.concurrent.TimeUnit;
56import java.util.concurrent.TimeoutException;
Andrea Campanella101417d2015-12-11 17:58:07 -080057import java.util.concurrent.atomic.AtomicInteger;
Sean Condond2c8d472017-02-17 17:09:39 +000058
Aaron Kruglikov72db6422017-02-13 12:16:51 -080059import java.util.regex.Pattern;
60import java.util.regex.Matcher;
Andrea Campanella101417d2015-12-11 17:58:07 -080061
andreaeb70a942015-10-16 21:34:46 -070062/**
63 * Implementation of a NETCONF session to talk to a device.
64 */
65public class NetconfSessionImpl implements NetconfSession {
66
Andrea Campanella101417d2015-12-11 17:58:07 -080067 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070068 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080069
Andrea Campanella101417d2015-12-11 17:58:07 -080070 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080071 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080072 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080073 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080074 private static final String END_OF_RPC_OPEN_TAG = "\">";
75 private static final String EQUAL = "=";
76 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090077 private static final String RPC_OPEN = "<rpc ";
78 private static final String RPC_CLOSE = "</rpc>";
79 private static final String GET_OPEN = "<get>";
80 private static final String GET_CLOSE = "</get>";
81 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
82 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090083 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
84 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090085 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
86 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090087 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
88 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
89 private static final String TARGET_OPEN = "<target>";
90 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000091 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 +090092 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080093 private static final String XML_HEADER =
94 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090095 private static final String NETCONF_BASE_NAMESPACE =
96 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
97 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
98 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090099 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
100 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -0700101
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800102 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
Sean Condond2c8d472017-02-17 17:09:39 +0000103
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800104 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
105 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
106
107 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
108 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200109 private static final String HASH = "#";
110 private static final String LF = "\n";
111 private static final String LESS_THAN = "<";
112 private static final String MSGLEN_REGEX_PATTERN = "\n#\\d+\n";
113 private static final String NETCONF_10_CAPABILITY = "urn:ietf:params:netconf:base:1.0";
114 protected static final String NETCONF_11_CAPABILITY = "urn:ietf:params:netconf:base:1.1";
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800115
116 private String sessionID;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700117 private final AtomicInteger messageIdInteger = new AtomicInteger(1);
andreaeb70a942015-10-16 21:34:46 -0700118 private Connection netconfConnection;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700119 protected final NetconfDeviceInfo deviceInfo;
andreaeb70a942015-10-16 21:34:46 -0700120 private Session sshSession;
121 private boolean connectionActive;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800122 private Iterable<String> onosCapabilities =
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200123 ImmutableList.of(NETCONF_10_CAPABILITY, NETCONF_11_CAPABILITY);
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800124
125 /* NOTE: the "serverHelloResponseOld" is deprecated in 1.10.0 and should eventually be removed */
126 @Deprecated
127 private String serverHelloResponseOld;
128 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
helenyrwu0407c642016-06-09 12:01:30 -0700129 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -0800130 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200131 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -0700132 private boolean subscriptionConnected = false;
Andrea Campanellac3627842017-04-04 18:06:54 +0200133 private String notificationFilterSchema = null;
andreaeb70a942015-10-16 21:34:46 -0700134
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700135 private final Collection<NetconfDeviceOutputEventListener> primaryListeners =
136 new CopyOnWriteArrayList<>();
137 private final Collection<NetconfSession> children =
138 new CopyOnWriteArrayList<>();
139
Sean Condon54d82432017-07-26 22:27:25 +0100140 private int connectTimeout;
141 private int replyTimeout;
andreaeb70a942015-10-16 21:34:46 -0700142
Andrea Campanella101417d2015-12-11 17:58:07 -0800143 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700144 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900145 this.netconfConnection = null;
146 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700147 connectionActive = false;
Sean Condond2c8d472017-02-17 17:09:39 +0000148 replies = new ConcurrentHashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200149 errorReplies = new ArrayList<>();
Sean Condon54d82432017-07-26 22:27:25 +0100150
andreaeb70a942015-10-16 21:34:46 -0700151 startConnection();
152 }
153
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200154 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo, List<String> capabilities) throws NetconfException {
155 this.deviceInfo = deviceInfo;
156 this.netconfConnection = null;
157 this.sshSession = null;
158 connectionActive = false;
159 replies = new ConcurrentHashMap<>();
160 errorReplies = new ArrayList<>();
161 setOnosCapabilities(capabilities);
162 startConnection();
163
164 }
165
Andrea Campanella101417d2015-12-11 17:58:07 -0800166 private void startConnection() throws NetconfException {
Sean Condon54d82432017-07-26 22:27:25 +0100167 connectTimeout = deviceInfo.getConnectTimeoutSec().orElse(
168 NetconfControllerImpl.netconfConnectTimeout);
169 replyTimeout = deviceInfo.getReplyTimeoutSec().orElse(
170 NetconfControllerImpl.netconfReplyTimeout);
171 log.debug("Connecting to {} with timeouts C:{}, R:{}. I:connect-timeout", deviceInfo,
172 connectTimeout, replyTimeout);
173
andreaeb70a942015-10-16 21:34:46 -0700174 if (!connectionActive) {
175 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000176
Andrea Campanella101417d2015-12-11 17:58:07 -0800177 try {
Sean Condon334ad692016-12-13 17:56:56 +0000178 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800179 } catch (IOException e) {
Yuta HIGUCHI0184a7b2017-03-31 13:13:58 -0700180 throw new NetconfException("Cannot open a connection with device " + deviceInfo, e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800181 }
andreaeb70a942015-10-16 21:34:46 -0700182 boolean isAuthenticated;
183 try {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800184 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
185 log.debug("Authenticating with key file to device {} with username {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200186 deviceInfo.getDeviceId(), deviceInfo.name());
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800187 isAuthenticated = netconfConnection.authenticateWithPublicKey(
188 deviceInfo.name(), deviceInfo.getKeyFile(),
189 deviceInfo.password().equals("") ? null : deviceInfo.password());
190 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600191 log.debug("Authenticating with key to device {} with username {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200192 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700193 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600194 deviceInfo.name(), deviceInfo.getKey(),
195 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700196 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600197 log.debug("Authenticating to device {} with username {} with password",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200198 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700199 isAuthenticated = netconfConnection.authenticateWithPassword(
200 deviceInfo.name(), deviceInfo.password());
201 }
202 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500203 log.error("Authentication connection to device {} failed",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200204 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800205 throw new NetconfException("Authentication connection to device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200206 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700207 }
208
209 connectionActive = true;
210 Preconditions.checkArgument(isAuthenticated,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200211 "Authentication to device %s with username " +
212 "%s failed",
213 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700214 startSshSession();
215 }
216 }
217
Andrea Campanella101417d2015-12-11 17:58:07 -0800218 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700219 try {
220 sshSession = netconfConnection.openSession();
221 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700222 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200223 sshSession.getStderr(), deviceInfo,
224 new NetconfSessionDelegateImpl(),
225 replies);
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700226 this.addDeviceOutputListener(new FilteringNetconfDeviceOutputEventListener(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700227 sendHello();
228 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200229 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800230 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200231 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700232 }
233 }
234
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900235
236 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700237 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
238 boolean openNewSession = false;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800239 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700240 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
241 openNewSession = true;
242
243 } else if (subscriptionConnected &&
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200244 notificationFilterSchema != null &&
245 !Objects.equal(filterSchema, notificationFilterSchema)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700246 // interleave supported and existing filter is NOT "no filtering"
247 // and was requested with different filtering schema
248 log.info("Cannot use existing session for subscription {} ({})",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200249 deviceInfo, filterSchema);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700250 openNewSession = true;
helenyrwu0407c642016-06-09 12:01:30 -0700251 }
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700252
253 if (openNewSession) {
254 log.info("Creating notification session to {} with filter {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200255 deviceInfo, filterSchema);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700256 NetconfSession child = new NotificationSession(deviceInfo);
257
258 child.addDeviceOutputListener(new NotificationForwarder());
259
260 child.startSubscription(filterSchema);
261 children.add(child);
262 return;
263 }
264
265 // request to start interleaved notification session
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900266 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700267 if (!checkReply(reply)) {
268 throw new NetconfException("Subscription not successful with device "
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200269 + deviceInfo + " with reply " + reply);
helenyrwu0407c642016-06-09 12:01:30 -0700270 }
271 subscriptionConnected = true;
272 }
273
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900274 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700275 public void startSubscription() throws NetconfException {
276 if (!subscriptionConnected) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700277 startSubscriptionStream(null);
helenyrwu0407c642016-06-09 12:01:30 -0700278 }
279 streamHandler.setEnableNotifications(true);
280 }
281
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900282 @Beta
283 @Override
284 public void startSubscription(String filterSchema) throws NetconfException {
285 if (!subscriptionConnected) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200286 notificationFilterSchema = filterSchema;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700287 startSubscriptionStream(filterSchema);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900288 }
289 streamHandler.setEnableNotifications(true);
290 }
291
292 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700293 protected String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700294 StringBuilder subscriptionbuffer = new StringBuilder();
295 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
296 subscriptionbuffer.append(" <create-subscription\n");
297 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900298 // FIXME Only subtree filtering supported at the moment.
299 if (filterSchema != null) {
300 subscriptionbuffer.append(" ");
301 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
302 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
303 subscriptionbuffer.append(" ");
304 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
305 }
helenyrwu0407c642016-06-09 12:01:30 -0700306 subscriptionbuffer.append(" </create-subscription>\n");
307 subscriptionbuffer.append("</rpc>\n");
308 subscriptionbuffer.append(ENDPATTERN);
309 return subscriptionbuffer.toString();
310 }
311
312 @Override
313 public void endSubscription() throws NetconfException {
314 if (subscriptionConnected) {
315 streamHandler.setEnableNotifications(false);
316 } else {
317 throw new NetconfException("Subscription does not exist.");
318 }
319 }
320
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800321 private void sendHello() throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700322 serverHelloResponseOld = sendRequest(createHelloString(), true);
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800323 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
324 while (capabilityMatcher.find()) {
325 deviceCapabilities.add(capabilityMatcher.group(1));
326 }
327 sessionID = String.valueOf(-1);
328 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
329 if (sessionIDMatcher.find()) {
330 sessionID = sessionIDMatcher.group(1);
331 } else {
332 throw new NetconfException("Missing SessionID in server hello " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200333 "reponse.");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800334 }
335
andreaeb70a942015-10-16 21:34:46 -0700336 }
337
338 private String createHelloString() {
339 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800340 hellobuffer.append(XML_HEADER);
341 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700342 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
343 hellobuffer.append(" <capabilities>\n");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800344 onosCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800345 cap -> hellobuffer.append(" <capability>")
346 .append(cap)
347 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700348 hellobuffer.append(" </capabilities>\n");
349 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800350 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700351 return hellobuffer.toString();
352
353 }
354
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700355 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200356 public void checkAndReestablish() throws NetconfException {
357 if (sshSession.getState() != Channel.STATE_OPEN) {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800358 try {
Andrea Campanellac3627842017-04-04 18:06:54 +0200359 log.debug("Trying to reopen the Sesion with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800360 startSshSession();
Andrea Campanellac3627842017-04-04 18:06:54 +0200361 } catch (IOException | IllegalStateException e) {
362 log.debug("Trying to reopen the Connection with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800363 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200364 connectionActive = false;
365 replies.clear();
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800366 startConnection();
Andrea Campanellac3627842017-04-04 18:06:54 +0200367 if (subscriptionConnected) {
368 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
369 subscriptionConnected = false;
370 startSubscription(notificationFilterSchema);
371 }
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800372 } catch (IOException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200373 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800374 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800375 }
376 }
377 }
378 }
379
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200380 /**
381 * Validate and format message according to chunked framing mechanism.
382 *
383 * @param request to format
384 * @return formated message
385 */
386 private static String validateChunkedMessage(String request) {
387 if (request.endsWith(ENDPATTERN)) {
388 request = request.substring(0, request.length() - ENDPATTERN.length());
389 }
390 if (!request.startsWith(LF + HASH)) {
391 request = LF + HASH + request.length() + LF + request + LF + HASH + HASH + LF;
392 }
393 return request;
394 }
395
396 /**
397 * Validate and format netconf message.
398 *
399 * @param request to format
400 * @return formated message
401 */
402 private String validateNetconfMessage(String request) {
403 if (deviceCapabilities.contains(NETCONF_11_CAPABILITY)) {
404 request = validateChunkedMessage(request);
405 } else {
406 if (!request.contains(ENDPATTERN)) {
407 request = request + NEW_LINE + ENDPATTERN;
408 }
409 }
410 return request;
411 }
412
andreaeb70a942015-10-16 21:34:46 -0700413 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800414 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800415 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300416 checkReply(reply);
417 return reply;
andreaeb70a942015-10-16 21:34:46 -0700418 }
419
420 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000421 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800422 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000423 return streamHandler.sendMessage(request);
424 }
425
426 private CompletableFuture<String> request(String request, int messageId) {
427 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800428 }
429
430 private String sendRequest(String request) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200431 request = validateNetconfMessage(request);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700432 return sendRequest(request, false);
433 }
434
435 private String sendRequest(String request, boolean isHello) throws NetconfException {
Andrea Campanellac3627842017-04-04 18:06:54 +0200436 checkAndReestablish();
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700437 int messageId = -1;
438 if (!isHello) {
439 messageId = messageIdInteger.getAndIncrement();
440 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800441 request = formatXmlHeader(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200442 request = formatRequestMessageId(request, messageId);
Sean Condond2c8d472017-02-17 17:09:39 +0000443 CompletableFuture<String> futureReply = request(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800444 String rp;
445 try {
Sean Condon54d82432017-07-26 22:27:25 +0100446 log.debug("Sending request to NETCONF with timeout {} for {}",
447 replyTimeout, deviceInfo.name());
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300448 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000449 replies.remove(messageId);
Sean Condon7347de92017-07-21 12:17:25 +0100450 } catch (InterruptedException e) {
451 Thread.currentThread().interrupt();
452 throw new NetconfException("Interrupted waiting for reply for request" + request, e);
453 } catch (TimeoutException e) {
Sean Condon54d82432017-07-26 22:27:25 +0100454 throw new NetconfException("Timed out waiting for reply for request " +
455 request + " after " + replyTimeout + " sec.", e);
Sean Condon7347de92017-07-21 12:17:25 +0100456 } catch (ExecutionException e) {
457 log.warn("Closing session {} for {} due to unexpected Error", sessionID, deviceInfo, e);
458
459 netconfConnection.close(); //Closes the socket which should interrupt NetconfStreamThread
460 sshSession.close();
461
462 NetconfDeviceOutputEvent event = new NetconfDeviceOutputEvent(
463 NetconfDeviceOutputEvent.Type.SESSION_CLOSED,
464 null, "Closed due to unexpected error " + e.getCause(),
465 Optional.of(-1), deviceInfo);
466 publishEvent(event);
467 replies.clear();
468 errorReplies.clear();
469
470 throw new NetconfException("Closing session " + sessionID + " for " + deviceInfo +
471 " for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800472 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800473 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800474 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800475 }
476
Sean Condond2c8d472017-02-17 17:09:39 +0000477 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800478 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000479 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800480 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200481 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800482 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
483 //FIXME find out a better way to enforce the presence of message-id
484 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000485 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800486 }
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200487 request = updateRequestLength(request);
488 return request;
489 }
490
491 private String updateRequestLength(String request) {
492 if (request.contains(LF + HASH + HASH + LF)) {
493 int oldLen = Integer.parseInt(request.split(HASH)[1].split(LF)[0]);
494 String rpcWithEnding = request.substring(request.indexOf(LESS_THAN));
495 String firstBlock = request.split(MSGLEN_REGEX_PATTERN)[1].split(LF + HASH + HASH + LF)[0];
496 int newLen = 0;
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700497 newLen = firstBlock.getBytes(UTF_8).length;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200498 if (oldLen != newLen) {
499 return LF + HASH + newLen + LF + rpcWithEnding;
500 }
501 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800502 return request;
503 }
504
505 private String formatXmlHeader(String request) {
506 if (!request.contains(XML_HEADER)) {
507 //FIXME if application provieds his own XML header of different type there is a clash
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200508 if (request.startsWith(LF + HASH)) {
509 request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
510 } else {
511 request = XML_HEADER + "\n" + request;
512 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800513 }
514 return request;
515 }
516
Andrea Campanella101417d2015-12-11 17:58:07 -0800517 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900518 public String doWrappedRpc(String request) throws NetconfException {
519 StringBuilder rpc = new StringBuilder(XML_HEADER);
520 rpc.append(RPC_OPEN);
521 rpc.append(MESSAGE_ID_STRING);
522 rpc.append(EQUAL);
523 rpc.append("\"");
524 rpc.append(messageIdInteger.get());
525 rpc.append("\" ");
526 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
527 rpc.append(request);
528 rpc.append(RPC_CLOSE).append(NEW_LINE);
529 rpc.append(ENDPATTERN);
530 String reply = sendRequest(rpc.toString());
531 checkReply(reply);
532 return reply;
533 }
534
535 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800536 public String get(String request) throws NetconfException {
537 return requestSync(request);
538 }
539
540 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900541 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
542 StringBuilder rpc = new StringBuilder(XML_HEADER);
543 rpc.append(RPC_OPEN);
544 rpc.append(MESSAGE_ID_STRING);
545 rpc.append(EQUAL);
546 rpc.append("\"");
547 rpc.append(messageIdInteger.get());
548 rpc.append("\" ");
549 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
550 rpc.append(GET_OPEN).append(NEW_LINE);
551 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900552 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900553 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900554 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900555 }
556 if (withDefaultsMode != null) {
557 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
558 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
559 }
560 rpc.append(GET_CLOSE).append(NEW_LINE);
561 rpc.append(RPC_CLOSE).append(NEW_LINE);
562 rpc.append(ENDPATTERN);
563 String reply = sendRequest(rpc.toString());
564 checkReply(reply);
565 return reply;
566 }
567
568 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700569 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300570 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700571 }
572
573 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700574 public String getConfig(DatastoreId netconfTargetConfig,
575 String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800576 StringBuilder rpc = new StringBuilder(XML_HEADER);
577 rpc.append("<rpc ");
578 rpc.append(MESSAGE_ID_STRING);
579 rpc.append(EQUAL);
580 rpc.append("\"");
581 rpc.append(messageIdInteger.get());
582 rpc.append("\" ");
583 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700584 rpc.append("<get-config>\n");
585 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300586 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700587 rpc.append("</source>");
588 if (configurationSchema != null) {
589 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800590 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700591 rpc.append("</filter>\n");
592 }
593 rpc.append("</get-config>\n");
594 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800595 rpc.append(ENDPATTERN);
596 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800597 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700598 }
599
600 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800601 public boolean editConfig(String newConfiguration) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200602 if (!newConfiguration.endsWith(ENDPATTERN)) {
603 newConfiguration = newConfiguration + ENDPATTERN;
604 }
Andrea Campanella101417d2015-12-11 17:58:07 -0800605 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700606 }
607
608 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700609 public boolean editConfig(DatastoreId netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800610 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800611 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800612 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900613 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800614 rpc.append(MESSAGE_ID_STRING);
615 rpc.append(EQUAL);
616 rpc.append("\"");
617 rpc.append(messageIdInteger.get());
618 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900619 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
620 rpc.append(EDIT_CONFIG_OPEN).append("\n");
621 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300622 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900623 rpc.append(TARGET_CLOSE).append("\n");
624 if (mode != null) {
625 rpc.append(DEFAULT_OPERATION_OPEN);
626 rpc.append(mode);
627 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
628 }
629 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800630 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900631 rpc.append(CONFIG_CLOSE).append("\n");
632 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
633 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800634 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500635 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900636 String reply = sendRequest(rpc.toString());
637 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800638 }
639
640 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700641 public boolean copyConfig(DatastoreId destination,
642 DatastoreId source)
643 throws NetconfException {
644 return bareCopyConfig(destination.asXml(), source.asXml());
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300645 }
646
647 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700648 public boolean copyConfig(DatastoreId netconfTargetConfig,
649 String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800650 throws NetconfException {
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700651 return bareCopyConfig(netconfTargetConfig.asXml(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200652 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700653 }
654
655 @Override
656 public boolean copyConfig(String netconfTargetConfig,
657 String newConfiguration) throws NetconfException {
658 return bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200659 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700660 }
661
662 /**
663 * Normalize String parameter passed to copy-config API.
664 * <p>
665 * Provided for backward compatibility purpose
666 *
667 * @param input passed to copyConfig API
668 * @return XML likely to be suitable for copy-config source or target
669 */
670 private static CharSequence normalizeCopyConfigParam(String input) {
671 input = input.trim();
672 if (input.startsWith("<url")) {
673 return input;
674 } else if (!input.startsWith("<")) {
675 // assume it is a datastore name
676 return DatastoreId.datastore(input).asXml();
677 } else if (!input.startsWith("<config>")) {
678 return "<config>" + input + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700679 }
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700680 return input;
681 }
682
683 private boolean bareCopyConfig(CharSequence target,
684 CharSequence source)
685 throws NetconfException {
686
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800687 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400688 rpc.append(RPC_OPEN);
689 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700690 rpc.append("<copy-config>");
691 rpc.append("<target>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700692 rpc.append(target);
andreaeb70a942015-10-16 21:34:46 -0700693 rpc.append("</target>");
694 rpc.append("<source>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700695 rpc.append(source);
andreaeb70a942015-10-16 21:34:46 -0700696 rpc.append("</source>");
697 rpc.append("</copy-config>");
698 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800699 rpc.append(ENDPATTERN);
700 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700701 }
702
703 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700704 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
705 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
andreaeb70a942015-10-16 21:34:46 -0700706 log.warn("Target configuration for delete operation can't be \"running\"",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200707 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700708 return false;
709 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800710 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700711 rpc.append("<rpc>");
712 rpc.append("<delete-config>");
713 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300714 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700715 rpc.append("</target>");
716 rpc.append("</delete-config>");
717 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800718 rpc.append(ENDPATTERN);
719 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700720 }
721
722 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700723 public boolean lock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800724 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700725 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700726 rpc.append("<lock>");
727 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700728 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700729 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700730 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700731 rpc.append("</target>");
732 rpc.append("</lock>");
733 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800734 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700735 String lockReply = sendRequest(rpc.toString());
736 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700737 }
738
739 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700740 public boolean unlock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800741 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700742 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700743 rpc.append("<unlock>");
744 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700745 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700746 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700747 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700748 rpc.append("</target>");
749 rpc.append("</unlock>");
750 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800751 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700752 String unlockReply = sendRequest(rpc.toString());
753 return checkReply(unlockReply);
754 }
755
756 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800757 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700758 return close(false);
759 }
760
Andrea Campanella101417d2015-12-11 17:58:07 -0800761 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700762 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700763 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700764 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700765 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700766 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700767 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700768 }
andreaeb70a942015-10-16 21:34:46 -0700769 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800770 rpc.append(ENDPATTERN);
771 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700772 }
773
774 @Override
775 public String getSessionId() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800776 return sessionID;
andreaeb70a942015-10-16 21:34:46 -0700777 }
778
779 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800780 public Set<String> getDeviceCapabilitiesSet() {
781 return Collections.unmodifiableSet(deviceCapabilities);
782 }
783
andreaeb70a942015-10-16 21:34:46 -0700784 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800785 public void setOnosCapabilities(Iterable<String> capabilities) {
786 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700787 }
788
Andrea Campanella101417d2015-12-11 17:58:07 -0800789 @Override
790 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700791 streamHandler.addDeviceEventListener(listener);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700792 primaryListeners.add(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800793 }
794
795 @Override
Sean Condon54d82432017-07-26 22:27:25 +0100796 public int timeoutConnectSec() {
797 return connectTimeout;
798 }
799
800 @Override
801 public int timeoutReplySec() {
802 return replyTimeout;
803 }
804
805 /**
806 * Idle timeout is not settable on ETZ_SSH - the valuse used is the connect timeout.
807 */
808 @Override
809 public int timeoutIdleSec() {
810 return connectTimeout;
811 }
812
813 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800814 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700815 primaryListeners.remove(listener);
helenyrwu0407c642016-06-09 12:01:30 -0700816 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800817 }
818
819 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700820 if (reply != null) {
821 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900822 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700823 return true;
824 } else if (reply.contains("<ok/>")
825 || (reply.contains("<rpc-error>")
826 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900827 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700828 return true;
829 }
830 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800831 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700832 return false;
833 }
834
Sean Condon7347de92017-07-21 12:17:25 +0100835 protected void publishEvent(NetconfDeviceOutputEvent event) {
836 primaryListeners.forEach(lsnr -> {
837 if (lsnr.isRelevant(event)) {
838 lsnr.event(event);
839 }
840 });
841 }
842
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700843 static class NotificationSession extends NetconfSessionImpl {
844
845 private String notificationFilter;
846
847 NotificationSession(NetconfDeviceInfo deviceInfo)
848 throws NetconfException {
849 super(deviceInfo);
850 }
851
852 @Override
853 protected void startSubscriptionStream(String filterSchema)
854 throws NetconfException {
855
856 notificationFilter = filterSchema;
857 requestSync(createSubscriptionString(filterSchema));
858 }
859
860 @Override
861 public String toString() {
862 return MoreObjects.toStringHelper(getClass())
863 .add("deviceInfo", deviceInfo)
864 .add("sessionID", getSessionId())
865 .add("notificationFilter", notificationFilter)
866 .toString();
867 }
868 }
869
870 /**
871 * Listener attached to child session for notification streaming.
872 *
873 * Forwards all notification event from child session to primary session
874 * listeners.
875 */
876 private final class NotificationForwarder
877 implements NetconfDeviceOutputEventListener {
878
879 @Override
880 public boolean isRelevant(NetconfDeviceOutputEvent event) {
881 return event.type() == Type.DEVICE_NOTIFICATION;
882 }
883
884 @Override
885 public void event(NetconfDeviceOutputEvent event) {
Sean Condon7347de92017-07-21 12:17:25 +0100886 publishEvent(event);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700887 }
888 }
889
Andrea Campanella101417d2015-12-11 17:58:07 -0800890 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700891
Andrea Campanella101417d2015-12-11 17:58:07 -0800892 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200893 public void notify(NetconfDeviceOutputEvent event) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200894 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200895 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200896 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200897 if (!messageId.isPresent()) {
898 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800899 log.error("Device {} sent error reply {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200900 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200901 return;
902 }
903 CompletableFuture<String> completedReply =
904 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900905 if (completedReply != null) {
906 completedReply.complete(event.getMessagePayload());
907 }
andreaeb70a942015-10-16 21:34:46 -0700908 }
909 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700910
911 public static class SshNetconfSessionFactory implements NetconfSessionFactory {
912
913 @Override
914 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
915 return new NetconfSessionImpl(netconfDeviceInfo);
916 }
917 }
Sean Condon54d82432017-07-26 22:27:25 +0100918}