blob: 51a8cfd2d1dc2cd4e0a376b4343556372cda773e [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.
Yuta HIGUCHI371667d2017-09-05 17:30:51 -070064 *
65 * @deprecated in 1.12.0 use {@link NetconfSessionMinaImpl}
andreaeb70a942015-10-16 21:34:46 -070066 */
Yuta HIGUCHI371667d2017-09-05 17:30:51 -070067@Deprecated
andreaeb70a942015-10-16 21:34:46 -070068public class NetconfSessionImpl implements NetconfSession {
69
Andrea Campanella101417d2015-12-11 17:58:07 -080070 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070071 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080072
Andrea Campanella101417d2015-12-11 17:58:07 -080073 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080074 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080075 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080076 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080077 private static final String END_OF_RPC_OPEN_TAG = "\">";
78 private static final String EQUAL = "=";
79 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090080 private static final String RPC_OPEN = "<rpc ";
81 private static final String RPC_CLOSE = "</rpc>";
82 private static final String GET_OPEN = "<get>";
83 private static final String GET_CLOSE = "</get>";
84 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
85 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090086 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
87 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090088 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
89 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090090 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
91 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
92 private static final String TARGET_OPEN = "<target>";
93 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000094 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 +090095 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080096 private static final String XML_HEADER =
97 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090098 private static final String NETCONF_BASE_NAMESPACE =
99 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
100 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
101 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900102 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
103 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -0700104
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800105 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
Sean Condond2c8d472017-02-17 17:09:39 +0000106
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800107 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
108 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
109
110 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
111 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200112 private static final String HASH = "#";
113 private static final String LF = "\n";
114 private static final String LESS_THAN = "<";
115 private static final String MSGLEN_REGEX_PATTERN = "\n#\\d+\n";
116 private static final String NETCONF_10_CAPABILITY = "urn:ietf:params:netconf:base:1.0";
117 protected static final String NETCONF_11_CAPABILITY = "urn:ietf:params:netconf:base:1.1";
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800118
119 private String sessionID;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700120 private final AtomicInteger messageIdInteger = new AtomicInteger(1);
andreaeb70a942015-10-16 21:34:46 -0700121 private Connection netconfConnection;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700122 protected final NetconfDeviceInfo deviceInfo;
andreaeb70a942015-10-16 21:34:46 -0700123 private Session sshSession;
124 private boolean connectionActive;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800125 private Iterable<String> onosCapabilities =
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200126 ImmutableList.of(NETCONF_10_CAPABILITY, NETCONF_11_CAPABILITY);
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800127
128 /* NOTE: the "serverHelloResponseOld" is deprecated in 1.10.0 and should eventually be removed */
129 @Deprecated
130 private String serverHelloResponseOld;
131 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
helenyrwu0407c642016-06-09 12:01:30 -0700132 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -0800133 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200134 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -0700135 private boolean subscriptionConnected = false;
Andrea Campanellac3627842017-04-04 18:06:54 +0200136 private String notificationFilterSchema = null;
andreaeb70a942015-10-16 21:34:46 -0700137
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700138 private final Collection<NetconfDeviceOutputEventListener> primaryListeners =
139 new CopyOnWriteArrayList<>();
140 private final Collection<NetconfSession> children =
141 new CopyOnWriteArrayList<>();
142
Sean Condon54d82432017-07-26 22:27:25 +0100143 private int connectTimeout;
144 private int replyTimeout;
andreaeb70a942015-10-16 21:34:46 -0700145
Andrea Campanella101417d2015-12-11 17:58:07 -0800146 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700147 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900148 this.netconfConnection = null;
149 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700150 connectionActive = false;
Sean Condond2c8d472017-02-17 17:09:39 +0000151 replies = new ConcurrentHashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200152 errorReplies = new ArrayList<>();
Sean Condon54d82432017-07-26 22:27:25 +0100153
andreaeb70a942015-10-16 21:34:46 -0700154 startConnection();
155 }
156
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200157 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo, List<String> capabilities) throws NetconfException {
158 this.deviceInfo = deviceInfo;
159 this.netconfConnection = null;
160 this.sshSession = null;
161 connectionActive = false;
162 replies = new ConcurrentHashMap<>();
163 errorReplies = new ArrayList<>();
164 setOnosCapabilities(capabilities);
165 startConnection();
166
167 }
168
Andrea Campanella101417d2015-12-11 17:58:07 -0800169 private void startConnection() throws NetconfException {
Sean Condon54d82432017-07-26 22:27:25 +0100170 connectTimeout = deviceInfo.getConnectTimeoutSec().orElse(
171 NetconfControllerImpl.netconfConnectTimeout);
172 replyTimeout = deviceInfo.getReplyTimeoutSec().orElse(
173 NetconfControllerImpl.netconfReplyTimeout);
174 log.debug("Connecting to {} with timeouts C:{}, R:{}. I:connect-timeout", deviceInfo,
175 connectTimeout, replyTimeout);
176
andreaeb70a942015-10-16 21:34:46 -0700177 if (!connectionActive) {
178 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000179
Andrea Campanella101417d2015-12-11 17:58:07 -0800180 try {
Sean Condon334ad692016-12-13 17:56:56 +0000181 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800182 } catch (IOException e) {
Yuta HIGUCHI0184a7b2017-03-31 13:13:58 -0700183 throw new NetconfException("Cannot open a connection with device " + deviceInfo, e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800184 }
andreaeb70a942015-10-16 21:34:46 -0700185 boolean isAuthenticated;
186 try {
Ray Milkeyd4b51c22018-02-08 11:36:43 -0800187 if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600188 log.debug("Authenticating with key to device {} with username {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200189 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700190 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600191 deviceInfo.name(), deviceInfo.getKey(),
192 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700193 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600194 log.debug("Authenticating to device {} with username {} with password",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200195 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700196 isAuthenticated = netconfConnection.authenticateWithPassword(
197 deviceInfo.name(), deviceInfo.password());
198 }
199 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500200 log.error("Authentication connection to device {} failed",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200201 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800202 throw new NetconfException("Authentication connection to device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200203 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700204 }
205
206 connectionActive = true;
207 Preconditions.checkArgument(isAuthenticated,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200208 "Authentication to device %s with username " +
209 "%s failed",
210 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700211 startSshSession();
212 }
213 }
214
Andrea Campanella101417d2015-12-11 17:58:07 -0800215 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700216 try {
217 sshSession = netconfConnection.openSession();
218 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700219 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200220 sshSession.getStderr(), deviceInfo,
221 new NetconfSessionDelegateImpl(),
222 replies);
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700223 this.addDeviceOutputListener(new FilteringNetconfDeviceOutputEventListener(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700224 sendHello();
225 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200226 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800227 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200228 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700229 }
230 }
231
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900232
233 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700234 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
235 boolean openNewSession = false;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800236 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700237 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
238 openNewSession = true;
239
240 } else if (subscriptionConnected &&
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200241 notificationFilterSchema != null &&
242 !Objects.equal(filterSchema, notificationFilterSchema)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700243 // interleave supported and existing filter is NOT "no filtering"
244 // and was requested with different filtering schema
245 log.info("Cannot use existing session for subscription {} ({})",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200246 deviceInfo, filterSchema);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700247 openNewSession = true;
helenyrwu0407c642016-06-09 12:01:30 -0700248 }
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700249
250 if (openNewSession) {
251 log.info("Creating notification session to {} with filter {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200252 deviceInfo, filterSchema);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700253 NetconfSession child = new NotificationSession(deviceInfo);
254
255 child.addDeviceOutputListener(new NotificationForwarder());
256
257 child.startSubscription(filterSchema);
258 children.add(child);
259 return;
260 }
261
262 // request to start interleaved notification session
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900263 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700264 if (!checkReply(reply)) {
265 throw new NetconfException("Subscription not successful with device "
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200266 + deviceInfo + " with reply " + reply);
helenyrwu0407c642016-06-09 12:01:30 -0700267 }
268 subscriptionConnected = true;
269 }
270
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900271 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700272 public void startSubscription() throws NetconfException {
273 if (!subscriptionConnected) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700274 startSubscriptionStream(null);
helenyrwu0407c642016-06-09 12:01:30 -0700275 }
276 streamHandler.setEnableNotifications(true);
277 }
278
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900279 @Beta
280 @Override
281 public void startSubscription(String filterSchema) throws NetconfException {
282 if (!subscriptionConnected) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200283 notificationFilterSchema = filterSchema;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700284 startSubscriptionStream(filterSchema);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900285 }
286 streamHandler.setEnableNotifications(true);
287 }
288
289 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700290 protected String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700291 StringBuilder subscriptionbuffer = new StringBuilder();
292 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
293 subscriptionbuffer.append(" <create-subscription\n");
294 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900295 // FIXME Only subtree filtering supported at the moment.
296 if (filterSchema != null) {
297 subscriptionbuffer.append(" ");
298 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
299 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
300 subscriptionbuffer.append(" ");
301 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
302 }
helenyrwu0407c642016-06-09 12:01:30 -0700303 subscriptionbuffer.append(" </create-subscription>\n");
304 subscriptionbuffer.append("</rpc>\n");
305 subscriptionbuffer.append(ENDPATTERN);
306 return subscriptionbuffer.toString();
307 }
308
309 @Override
310 public void endSubscription() throws NetconfException {
311 if (subscriptionConnected) {
312 streamHandler.setEnableNotifications(false);
313 } else {
314 throw new NetconfException("Subscription does not exist.");
315 }
316 }
317
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800318 private void sendHello() throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700319 serverHelloResponseOld = sendRequest(createHelloString(), true);
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800320 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
321 while (capabilityMatcher.find()) {
322 deviceCapabilities.add(capabilityMatcher.group(1));
323 }
324 sessionID = String.valueOf(-1);
325 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
326 if (sessionIDMatcher.find()) {
327 sessionID = sessionIDMatcher.group(1);
328 } else {
329 throw new NetconfException("Missing SessionID in server hello " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200330 "reponse.");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800331 }
332
andreaeb70a942015-10-16 21:34:46 -0700333 }
334
335 private String createHelloString() {
336 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800337 hellobuffer.append(XML_HEADER);
338 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700339 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
340 hellobuffer.append(" <capabilities>\n");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800341 onosCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800342 cap -> hellobuffer.append(" <capability>")
343 .append(cap)
344 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700345 hellobuffer.append(" </capabilities>\n");
346 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800347 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700348 return hellobuffer.toString();
349
350 }
351
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700352 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200353 public void checkAndReestablish() throws NetconfException {
354 if (sshSession.getState() != Channel.STATE_OPEN) {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800355 try {
Andrea Campanellac3627842017-04-04 18:06:54 +0200356 log.debug("Trying to reopen the Sesion with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800357 startSshSession();
Yuta HIGUCHI234eaf32017-09-06 13:45:05 -0700358 } catch (NetconfException | IllegalStateException e) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200359 log.debug("Trying to reopen the Connection with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800360 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200361 connectionActive = false;
362 replies.clear();
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800363 startConnection();
Andrea Campanellac3627842017-04-04 18:06:54 +0200364 if (subscriptionConnected) {
365 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
366 subscriptionConnected = false;
367 startSubscription(notificationFilterSchema);
368 }
Yuta HIGUCHI234eaf32017-09-06 13:45:05 -0700369 } catch (NetconfException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200370 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800371 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800372 }
373 }
374 }
375 }
376
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200377 /**
378 * Validate and format message according to chunked framing mechanism.
379 *
380 * @param request to format
381 * @return formated message
382 */
383 private static String validateChunkedMessage(String request) {
384 if (request.endsWith(ENDPATTERN)) {
385 request = request.substring(0, request.length() - ENDPATTERN.length());
386 }
387 if (!request.startsWith(LF + HASH)) {
388 request = LF + HASH + request.length() + LF + request + LF + HASH + HASH + LF;
389 }
390 return request;
391 }
392
393 /**
394 * Validate and format netconf message.
395 *
396 * @param request to format
397 * @return formated message
398 */
399 private String validateNetconfMessage(String request) {
400 if (deviceCapabilities.contains(NETCONF_11_CAPABILITY)) {
401 request = validateChunkedMessage(request);
402 } else {
403 if (!request.contains(ENDPATTERN)) {
404 request = request + NEW_LINE + ENDPATTERN;
405 }
406 }
407 return request;
408 }
409
andreaeb70a942015-10-16 21:34:46 -0700410 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800411 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800412 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300413 checkReply(reply);
414 return reply;
andreaeb70a942015-10-16 21:34:46 -0700415 }
416
417 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000418 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800419 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000420 return streamHandler.sendMessage(request);
421 }
422
423 private CompletableFuture<String> request(String request, int messageId) {
424 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800425 }
426
427 private String sendRequest(String request) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200428 request = validateNetconfMessage(request);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700429 return sendRequest(request, false);
430 }
431
432 private String sendRequest(String request, boolean isHello) throws NetconfException {
Andrea Campanellac3627842017-04-04 18:06:54 +0200433 checkAndReestablish();
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700434 int messageId = -1;
435 if (!isHello) {
436 messageId = messageIdInteger.getAndIncrement();
437 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800438 request = formatXmlHeader(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200439 request = formatRequestMessageId(request, messageId);
Sean Condond2c8d472017-02-17 17:09:39 +0000440 CompletableFuture<String> futureReply = request(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800441 String rp;
442 try {
Sean Condon54d82432017-07-26 22:27:25 +0100443 log.debug("Sending request to NETCONF with timeout {} for {}",
444 replyTimeout, deviceInfo.name());
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300445 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000446 replies.remove(messageId);
Sean Condon7347de92017-07-21 12:17:25 +0100447 } catch (InterruptedException e) {
448 Thread.currentThread().interrupt();
449 throw new NetconfException("Interrupted waiting for reply for request" + request, e);
450 } catch (TimeoutException e) {
Sean Condon54d82432017-07-26 22:27:25 +0100451 throw new NetconfException("Timed out waiting for reply for request " +
452 request + " after " + replyTimeout + " sec.", e);
Sean Condon7347de92017-07-21 12:17:25 +0100453 } catch (ExecutionException e) {
454 log.warn("Closing session {} for {} due to unexpected Error", sessionID, deviceInfo, e);
455
456 netconfConnection.close(); //Closes the socket which should interrupt NetconfStreamThread
457 sshSession.close();
458
459 NetconfDeviceOutputEvent event = new NetconfDeviceOutputEvent(
460 NetconfDeviceOutputEvent.Type.SESSION_CLOSED,
461 null, "Closed due to unexpected error " + e.getCause(),
462 Optional.of(-1), deviceInfo);
463 publishEvent(event);
464 replies.clear();
465 errorReplies.clear();
466
467 throw new NetconfException("Closing session " + sessionID + " for " + deviceInfo +
468 " for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800469 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800470 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800471 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800472 }
473
Sean Condond2c8d472017-02-17 17:09:39 +0000474 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800475 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000476 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800477 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200478 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800479 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
480 //FIXME find out a better way to enforce the presence of message-id
481 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000482 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800483 }
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200484 request = updateRequestLength(request);
485 return request;
486 }
487
488 private String updateRequestLength(String request) {
489 if (request.contains(LF + HASH + HASH + LF)) {
490 int oldLen = Integer.parseInt(request.split(HASH)[1].split(LF)[0]);
491 String rpcWithEnding = request.substring(request.indexOf(LESS_THAN));
492 String firstBlock = request.split(MSGLEN_REGEX_PATTERN)[1].split(LF + HASH + HASH + LF)[0];
493 int newLen = 0;
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700494 newLen = firstBlock.getBytes(UTF_8).length;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200495 if (oldLen != newLen) {
496 return LF + HASH + newLen + LF + rpcWithEnding;
497 }
498 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800499 return request;
500 }
501
502 private String formatXmlHeader(String request) {
503 if (!request.contains(XML_HEADER)) {
504 //FIXME if application provieds his own XML header of different type there is a clash
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200505 if (request.startsWith(LF + HASH)) {
506 request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
507 } else {
508 request = XML_HEADER + "\n" + request;
509 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800510 }
511 return request;
512 }
513
Andrea Campanella101417d2015-12-11 17:58:07 -0800514 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900515 public String doWrappedRpc(String request) throws NetconfException {
516 StringBuilder rpc = new StringBuilder(XML_HEADER);
517 rpc.append(RPC_OPEN);
518 rpc.append(MESSAGE_ID_STRING);
519 rpc.append(EQUAL);
520 rpc.append("\"");
521 rpc.append(messageIdInteger.get());
522 rpc.append("\" ");
523 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
524 rpc.append(request);
525 rpc.append(RPC_CLOSE).append(NEW_LINE);
526 rpc.append(ENDPATTERN);
527 String reply = sendRequest(rpc.toString());
528 checkReply(reply);
529 return reply;
530 }
531
532 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800533 public String get(String request) throws NetconfException {
534 return requestSync(request);
535 }
536
537 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900538 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
539 StringBuilder rpc = new StringBuilder(XML_HEADER);
540 rpc.append(RPC_OPEN);
541 rpc.append(MESSAGE_ID_STRING);
542 rpc.append(EQUAL);
543 rpc.append("\"");
544 rpc.append(messageIdInteger.get());
545 rpc.append("\" ");
546 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
547 rpc.append(GET_OPEN).append(NEW_LINE);
548 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900549 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900550 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900551 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900552 }
553 if (withDefaultsMode != null) {
554 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
555 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
556 }
557 rpc.append(GET_CLOSE).append(NEW_LINE);
558 rpc.append(RPC_CLOSE).append(NEW_LINE);
559 rpc.append(ENDPATTERN);
560 String reply = sendRequest(rpc.toString());
561 checkReply(reply);
562 return reply;
563 }
564
565 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700566 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300567 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700568 }
569
570 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700571 public String getConfig(DatastoreId netconfTargetConfig,
572 String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800573 StringBuilder rpc = new StringBuilder(XML_HEADER);
574 rpc.append("<rpc ");
575 rpc.append(MESSAGE_ID_STRING);
576 rpc.append(EQUAL);
577 rpc.append("\"");
578 rpc.append(messageIdInteger.get());
579 rpc.append("\" ");
580 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700581 rpc.append("<get-config>\n");
582 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300583 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700584 rpc.append("</source>");
585 if (configurationSchema != null) {
586 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800587 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700588 rpc.append("</filter>\n");
589 }
590 rpc.append("</get-config>\n");
591 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800592 rpc.append(ENDPATTERN);
593 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800594 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700595 }
596
597 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800598 public boolean editConfig(String newConfiguration) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200599 if (!newConfiguration.endsWith(ENDPATTERN)) {
600 newConfiguration = newConfiguration + ENDPATTERN;
601 }
Andrea Campanella101417d2015-12-11 17:58:07 -0800602 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700603 }
604
605 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700606 public boolean editConfig(DatastoreId netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800607 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800608 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800609 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900610 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800611 rpc.append(MESSAGE_ID_STRING);
612 rpc.append(EQUAL);
613 rpc.append("\"");
614 rpc.append(messageIdInteger.get());
615 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900616 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
617 rpc.append(EDIT_CONFIG_OPEN).append("\n");
618 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300619 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900620 rpc.append(TARGET_CLOSE).append("\n");
621 if (mode != null) {
622 rpc.append(DEFAULT_OPERATION_OPEN);
623 rpc.append(mode);
624 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
625 }
626 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800627 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900628 rpc.append(CONFIG_CLOSE).append("\n");
629 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
630 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800631 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500632 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900633 String reply = sendRequest(rpc.toString());
634 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800635 }
636
637 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700638 public boolean copyConfig(DatastoreId destination,
639 DatastoreId source)
640 throws NetconfException {
641 return bareCopyConfig(destination.asXml(), source.asXml());
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300642 }
643
644 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700645 public boolean copyConfig(DatastoreId netconfTargetConfig,
646 String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800647 throws NetconfException {
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700648 return bareCopyConfig(netconfTargetConfig.asXml(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200649 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700650 }
651
652 @Override
653 public boolean copyConfig(String netconfTargetConfig,
654 String newConfiguration) throws NetconfException {
655 return bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200656 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700657 }
658
659 /**
660 * Normalize String parameter passed to copy-config API.
661 * <p>
662 * Provided for backward compatibility purpose
663 *
664 * @param input passed to copyConfig API
665 * @return XML likely to be suitable for copy-config source or target
666 */
667 private static CharSequence normalizeCopyConfigParam(String input) {
668 input = input.trim();
669 if (input.startsWith("<url")) {
670 return input;
671 } else if (!input.startsWith("<")) {
672 // assume it is a datastore name
673 return DatastoreId.datastore(input).asXml();
674 } else if (!input.startsWith("<config>")) {
675 return "<config>" + input + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700676 }
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700677 return input;
678 }
679
680 private boolean bareCopyConfig(CharSequence target,
681 CharSequence source)
682 throws NetconfException {
683
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800684 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400685 rpc.append(RPC_OPEN);
686 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700687 rpc.append("<copy-config>");
688 rpc.append("<target>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700689 rpc.append(target);
andreaeb70a942015-10-16 21:34:46 -0700690 rpc.append("</target>");
691 rpc.append("<source>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700692 rpc.append(source);
andreaeb70a942015-10-16 21:34:46 -0700693 rpc.append("</source>");
694 rpc.append("</copy-config>");
695 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800696 rpc.append(ENDPATTERN);
697 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700698 }
699
700 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700701 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
702 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
andreaeb70a942015-10-16 21:34:46 -0700703 log.warn("Target configuration for delete operation can't be \"running\"",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200704 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700705 return false;
706 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800707 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700708 rpc.append("<rpc>");
709 rpc.append("<delete-config>");
710 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300711 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700712 rpc.append("</target>");
713 rpc.append("</delete-config>");
714 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800715 rpc.append(ENDPATTERN);
716 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700717 }
718
719 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700720 public boolean lock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800721 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700722 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700723 rpc.append("<lock>");
724 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700725 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700726 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700727 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700728 rpc.append("</target>");
729 rpc.append("</lock>");
730 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800731 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700732 String lockReply = sendRequest(rpc.toString());
733 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700734 }
735
736 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700737 public boolean unlock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800738 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700739 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700740 rpc.append("<unlock>");
741 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700742 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700743 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700744 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700745 rpc.append("</target>");
746 rpc.append("</unlock>");
747 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800748 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700749 String unlockReply = sendRequest(rpc.toString());
750 return checkReply(unlockReply);
751 }
752
753 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800754 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700755 return close(false);
756 }
757
Andrea Campanella101417d2015-12-11 17:58:07 -0800758 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700759 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700760 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700761 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700762 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700763 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700764 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700765 }
andreaeb70a942015-10-16 21:34:46 -0700766 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800767 rpc.append(ENDPATTERN);
768 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700769 }
770
771 @Override
772 public String getSessionId() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800773 return sessionID;
andreaeb70a942015-10-16 21:34:46 -0700774 }
775
776 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800777 public Set<String> getDeviceCapabilitiesSet() {
778 return Collections.unmodifiableSet(deviceCapabilities);
779 }
780
andreaeb70a942015-10-16 21:34:46 -0700781 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800782 public void setOnosCapabilities(Iterable<String> capabilities) {
783 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700784 }
785
Andrea Campanella101417d2015-12-11 17:58:07 -0800786 @Override
787 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700788 streamHandler.addDeviceEventListener(listener);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700789 primaryListeners.add(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800790 }
791
792 @Override
Sean Condon54d82432017-07-26 22:27:25 +0100793 public int timeoutConnectSec() {
794 return connectTimeout;
795 }
796
797 @Override
798 public int timeoutReplySec() {
799 return replyTimeout;
800 }
801
802 /**
803 * Idle timeout is not settable on ETZ_SSH - the valuse used is the connect timeout.
804 */
805 @Override
806 public int timeoutIdleSec() {
807 return connectTimeout;
808 }
809
810 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800811 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700812 primaryListeners.remove(listener);
helenyrwu0407c642016-06-09 12:01:30 -0700813 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800814 }
815
816 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700817 if (reply != null) {
818 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900819 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700820 return true;
821 } else if (reply.contains("<ok/>")
822 || (reply.contains("<rpc-error>")
823 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900824 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700825 return true;
826 }
827 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800828 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700829 return false;
830 }
831
Sean Condon7347de92017-07-21 12:17:25 +0100832 protected void publishEvent(NetconfDeviceOutputEvent event) {
833 primaryListeners.forEach(lsnr -> {
834 if (lsnr.isRelevant(event)) {
835 lsnr.event(event);
836 }
837 });
838 }
839
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700840 static class NotificationSession extends NetconfSessionImpl {
841
842 private String notificationFilter;
843
844 NotificationSession(NetconfDeviceInfo deviceInfo)
845 throws NetconfException {
846 super(deviceInfo);
847 }
848
849 @Override
850 protected void startSubscriptionStream(String filterSchema)
851 throws NetconfException {
852
853 notificationFilter = filterSchema;
854 requestSync(createSubscriptionString(filterSchema));
855 }
856
857 @Override
858 public String toString() {
859 return MoreObjects.toStringHelper(getClass())
860 .add("deviceInfo", deviceInfo)
861 .add("sessionID", getSessionId())
862 .add("notificationFilter", notificationFilter)
863 .toString();
864 }
865 }
866
867 /**
868 * Listener attached to child session for notification streaming.
869 *
870 * Forwards all notification event from child session to primary session
871 * listeners.
872 */
873 private final class NotificationForwarder
874 implements NetconfDeviceOutputEventListener {
875
876 @Override
877 public boolean isRelevant(NetconfDeviceOutputEvent event) {
878 return event.type() == Type.DEVICE_NOTIFICATION;
879 }
880
881 @Override
882 public void event(NetconfDeviceOutputEvent event) {
Sean Condon7347de92017-07-21 12:17:25 +0100883 publishEvent(event);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700884 }
885 }
886
Andrea Campanella101417d2015-12-11 17:58:07 -0800887 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700888
Andrea Campanella101417d2015-12-11 17:58:07 -0800889 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200890 public void notify(NetconfDeviceOutputEvent event) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200891 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200892 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200893 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200894 if (!messageId.isPresent()) {
895 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800896 log.error("Device {} sent error reply {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200897 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200898 return;
899 }
900 CompletableFuture<String> completedReply =
901 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900902 if (completedReply != null) {
903 completedReply.complete(event.getMessagePayload());
904 }
andreaeb70a942015-10-16 21:34:46 -0700905 }
906 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700907
908 public static class SshNetconfSessionFactory implements NetconfSessionFactory {
909
910 @Override
911 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
912 return new NetconfSessionImpl(netconfDeviceInfo);
913 }
914 }
Sean Condon54d82432017-07-26 22:27:25 +0100915}