blob: bb1a4ed454ea1fdcd9bbbcb6835deeb5c950b05c [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 {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800187 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
188 log.debug("Authenticating with key file to device {} with username {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200189 deviceInfo.getDeviceId(), deviceInfo.name());
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800190 isAuthenticated = netconfConnection.authenticateWithPublicKey(
191 deviceInfo.name(), deviceInfo.getKeyFile(),
192 deviceInfo.password().equals("") ? null : deviceInfo.password());
193 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600194 log.debug("Authenticating with key to device {} with username {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200195 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700196 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600197 deviceInfo.name(), deviceInfo.getKey(),
198 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700199 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600200 log.debug("Authenticating to device {} with username {} with password",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200201 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700202 isAuthenticated = netconfConnection.authenticateWithPassword(
203 deviceInfo.name(), deviceInfo.password());
204 }
205 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500206 log.error("Authentication connection to device {} failed",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200207 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800208 throw new NetconfException("Authentication connection to device " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200209 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700210 }
211
212 connectionActive = true;
213 Preconditions.checkArgument(isAuthenticated,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200214 "Authentication to device %s with username " +
215 "%s failed",
216 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700217 startSshSession();
218 }
219 }
220
Andrea Campanella101417d2015-12-11 17:58:07 -0800221 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700222 try {
223 sshSession = netconfConnection.openSession();
224 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700225 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200226 sshSession.getStderr(), deviceInfo,
227 new NetconfSessionDelegateImpl(),
228 replies);
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700229 this.addDeviceOutputListener(new FilteringNetconfDeviceOutputEventListener(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700230 sendHello();
231 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200232 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800233 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200234 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700235 }
236 }
237
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900238
239 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700240 protected void startSubscriptionStream(String filterSchema) throws NetconfException {
241 boolean openNewSession = false;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800242 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700243 log.info("Device {} doesn't support interleave, creating child session", deviceInfo);
244 openNewSession = true;
245
246 } else if (subscriptionConnected &&
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200247 notificationFilterSchema != null &&
248 !Objects.equal(filterSchema, notificationFilterSchema)) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700249 // interleave supported and existing filter is NOT "no filtering"
250 // and was requested with different filtering schema
251 log.info("Cannot use existing session for subscription {} ({})",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200252 deviceInfo, filterSchema);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700253 openNewSession = true;
helenyrwu0407c642016-06-09 12:01:30 -0700254 }
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700255
256 if (openNewSession) {
257 log.info("Creating notification session to {} with filter {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200258 deviceInfo, filterSchema);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700259 NetconfSession child = new NotificationSession(deviceInfo);
260
261 child.addDeviceOutputListener(new NotificationForwarder());
262
263 child.startSubscription(filterSchema);
264 children.add(child);
265 return;
266 }
267
268 // request to start interleaved notification session
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900269 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700270 if (!checkReply(reply)) {
271 throw new NetconfException("Subscription not successful with device "
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200272 + deviceInfo + " with reply " + reply);
helenyrwu0407c642016-06-09 12:01:30 -0700273 }
274 subscriptionConnected = true;
275 }
276
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900277 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700278 public void startSubscription() throws NetconfException {
279 if (!subscriptionConnected) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700280 startSubscriptionStream(null);
helenyrwu0407c642016-06-09 12:01:30 -0700281 }
282 streamHandler.setEnableNotifications(true);
283 }
284
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900285 @Beta
286 @Override
287 public void startSubscription(String filterSchema) throws NetconfException {
288 if (!subscriptionConnected) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200289 notificationFilterSchema = filterSchema;
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700290 startSubscriptionStream(filterSchema);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900291 }
292 streamHandler.setEnableNotifications(true);
293 }
294
295 @Beta
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700296 protected String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700297 StringBuilder subscriptionbuffer = new StringBuilder();
298 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
299 subscriptionbuffer.append(" <create-subscription\n");
300 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900301 // FIXME Only subtree filtering supported at the moment.
302 if (filterSchema != null) {
303 subscriptionbuffer.append(" ");
304 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
305 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
306 subscriptionbuffer.append(" ");
307 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
308 }
helenyrwu0407c642016-06-09 12:01:30 -0700309 subscriptionbuffer.append(" </create-subscription>\n");
310 subscriptionbuffer.append("</rpc>\n");
311 subscriptionbuffer.append(ENDPATTERN);
312 return subscriptionbuffer.toString();
313 }
314
315 @Override
316 public void endSubscription() throws NetconfException {
317 if (subscriptionConnected) {
318 streamHandler.setEnableNotifications(false);
319 } else {
320 throw new NetconfException("Subscription does not exist.");
321 }
322 }
323
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800324 private void sendHello() throws NetconfException {
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700325 serverHelloResponseOld = sendRequest(createHelloString(), true);
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800326 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
327 while (capabilityMatcher.find()) {
328 deviceCapabilities.add(capabilityMatcher.group(1));
329 }
330 sessionID = String.valueOf(-1);
331 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
332 if (sessionIDMatcher.find()) {
333 sessionID = sessionIDMatcher.group(1);
334 } else {
335 throw new NetconfException("Missing SessionID in server hello " +
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200336 "reponse.");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800337 }
338
andreaeb70a942015-10-16 21:34:46 -0700339 }
340
341 private String createHelloString() {
342 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800343 hellobuffer.append(XML_HEADER);
344 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700345 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
346 hellobuffer.append(" <capabilities>\n");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800347 onosCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800348 cap -> hellobuffer.append(" <capability>")
349 .append(cap)
350 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700351 hellobuffer.append(" </capabilities>\n");
352 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800353 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700354 return hellobuffer.toString();
355
356 }
357
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700358 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200359 public void checkAndReestablish() throws NetconfException {
360 if (sshSession.getState() != Channel.STATE_OPEN) {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800361 try {
Andrea Campanellac3627842017-04-04 18:06:54 +0200362 log.debug("Trying to reopen the Sesion with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800363 startSshSession();
Andrea Campanellac3627842017-04-04 18:06:54 +0200364 } catch (IOException | IllegalStateException e) {
365 log.debug("Trying to reopen the Connection with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800366 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200367 connectionActive = false;
368 replies.clear();
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800369 startConnection();
Andrea Campanellac3627842017-04-04 18:06:54 +0200370 if (subscriptionConnected) {
371 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
372 subscriptionConnected = false;
373 startSubscription(notificationFilterSchema);
374 }
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800375 } catch (IOException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200376 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800377 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800378 }
379 }
380 }
381 }
382
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200383 /**
384 * Validate and format message according to chunked framing mechanism.
385 *
386 * @param request to format
387 * @return formated message
388 */
389 private static String validateChunkedMessage(String request) {
390 if (request.endsWith(ENDPATTERN)) {
391 request = request.substring(0, request.length() - ENDPATTERN.length());
392 }
393 if (!request.startsWith(LF + HASH)) {
394 request = LF + HASH + request.length() + LF + request + LF + HASH + HASH + LF;
395 }
396 return request;
397 }
398
399 /**
400 * Validate and format netconf message.
401 *
402 * @param request to format
403 * @return formated message
404 */
405 private String validateNetconfMessage(String request) {
406 if (deviceCapabilities.contains(NETCONF_11_CAPABILITY)) {
407 request = validateChunkedMessage(request);
408 } else {
409 if (!request.contains(ENDPATTERN)) {
410 request = request + NEW_LINE + ENDPATTERN;
411 }
412 }
413 return request;
414 }
415
andreaeb70a942015-10-16 21:34:46 -0700416 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800417 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800418 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300419 checkReply(reply);
420 return reply;
andreaeb70a942015-10-16 21:34:46 -0700421 }
422
423 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000424 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800425 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000426 return streamHandler.sendMessage(request);
427 }
428
429 private CompletableFuture<String> request(String request, int messageId) {
430 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800431 }
432
433 private String sendRequest(String request) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200434 request = validateNetconfMessage(request);
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700435 return sendRequest(request, false);
436 }
437
438 private String sendRequest(String request, boolean isHello) throws NetconfException {
Andrea Campanellac3627842017-04-04 18:06:54 +0200439 checkAndReestablish();
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700440 int messageId = -1;
441 if (!isHello) {
442 messageId = messageIdInteger.getAndIncrement();
443 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800444 request = formatXmlHeader(request);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200445 request = formatRequestMessageId(request, messageId);
Sean Condond2c8d472017-02-17 17:09:39 +0000446 CompletableFuture<String> futureReply = request(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800447 String rp;
448 try {
Sean Condon54d82432017-07-26 22:27:25 +0100449 log.debug("Sending request to NETCONF with timeout {} for {}",
450 replyTimeout, deviceInfo.name());
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300451 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000452 replies.remove(messageId);
Sean Condon7347de92017-07-21 12:17:25 +0100453 } catch (InterruptedException e) {
454 Thread.currentThread().interrupt();
455 throw new NetconfException("Interrupted waiting for reply for request" + request, e);
456 } catch (TimeoutException e) {
Sean Condon54d82432017-07-26 22:27:25 +0100457 throw new NetconfException("Timed out waiting for reply for request " +
458 request + " after " + replyTimeout + " sec.", e);
Sean Condon7347de92017-07-21 12:17:25 +0100459 } catch (ExecutionException e) {
460 log.warn("Closing session {} for {} due to unexpected Error", sessionID, deviceInfo, e);
461
462 netconfConnection.close(); //Closes the socket which should interrupt NetconfStreamThread
463 sshSession.close();
464
465 NetconfDeviceOutputEvent event = new NetconfDeviceOutputEvent(
466 NetconfDeviceOutputEvent.Type.SESSION_CLOSED,
467 null, "Closed due to unexpected error " + e.getCause(),
468 Optional.of(-1), deviceInfo);
469 publishEvent(event);
470 replies.clear();
471 errorReplies.clear();
472
473 throw new NetconfException("Closing session " + sessionID + " for " + deviceInfo +
474 " for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800475 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800476 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800477 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800478 }
479
Sean Condond2c8d472017-02-17 17:09:39 +0000480 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800481 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000482 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800483 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200484 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800485 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
486 //FIXME find out a better way to enforce the presence of message-id
487 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000488 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800489 }
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200490 request = updateRequestLength(request);
491 return request;
492 }
493
494 private String updateRequestLength(String request) {
495 if (request.contains(LF + HASH + HASH + LF)) {
496 int oldLen = Integer.parseInt(request.split(HASH)[1].split(LF)[0]);
497 String rpcWithEnding = request.substring(request.indexOf(LESS_THAN));
498 String firstBlock = request.split(MSGLEN_REGEX_PATTERN)[1].split(LF + HASH + HASH + LF)[0];
499 int newLen = 0;
Yuta HIGUCHI15677982017-08-16 15:50:29 -0700500 newLen = firstBlock.getBytes(UTF_8).length;
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200501 if (oldLen != newLen) {
502 return LF + HASH + newLen + LF + rpcWithEnding;
503 }
504 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800505 return request;
506 }
507
508 private String formatXmlHeader(String request) {
509 if (!request.contains(XML_HEADER)) {
510 //FIXME if application provieds his own XML header of different type there is a clash
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200511 if (request.startsWith(LF + HASH)) {
512 request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
513 } else {
514 request = XML_HEADER + "\n" + request;
515 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800516 }
517 return request;
518 }
519
Andrea Campanella101417d2015-12-11 17:58:07 -0800520 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900521 public String doWrappedRpc(String request) throws NetconfException {
522 StringBuilder rpc = new StringBuilder(XML_HEADER);
523 rpc.append(RPC_OPEN);
524 rpc.append(MESSAGE_ID_STRING);
525 rpc.append(EQUAL);
526 rpc.append("\"");
527 rpc.append(messageIdInteger.get());
528 rpc.append("\" ");
529 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
530 rpc.append(request);
531 rpc.append(RPC_CLOSE).append(NEW_LINE);
532 rpc.append(ENDPATTERN);
533 String reply = sendRequest(rpc.toString());
534 checkReply(reply);
535 return reply;
536 }
537
538 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800539 public String get(String request) throws NetconfException {
540 return requestSync(request);
541 }
542
543 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900544 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
545 StringBuilder rpc = new StringBuilder(XML_HEADER);
546 rpc.append(RPC_OPEN);
547 rpc.append(MESSAGE_ID_STRING);
548 rpc.append(EQUAL);
549 rpc.append("\"");
550 rpc.append(messageIdInteger.get());
551 rpc.append("\" ");
552 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
553 rpc.append(GET_OPEN).append(NEW_LINE);
554 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900555 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900556 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900557 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900558 }
559 if (withDefaultsMode != null) {
560 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
561 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
562 }
563 rpc.append(GET_CLOSE).append(NEW_LINE);
564 rpc.append(RPC_CLOSE).append(NEW_LINE);
565 rpc.append(ENDPATTERN);
566 String reply = sendRequest(rpc.toString());
567 checkReply(reply);
568 return reply;
569 }
570
571 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700572 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300573 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700574 }
575
576 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700577 public String getConfig(DatastoreId netconfTargetConfig,
578 String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800579 StringBuilder rpc = new StringBuilder(XML_HEADER);
580 rpc.append("<rpc ");
581 rpc.append(MESSAGE_ID_STRING);
582 rpc.append(EQUAL);
583 rpc.append("\"");
584 rpc.append(messageIdInteger.get());
585 rpc.append("\" ");
586 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700587 rpc.append("<get-config>\n");
588 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300589 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700590 rpc.append("</source>");
591 if (configurationSchema != null) {
592 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800593 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700594 rpc.append("</filter>\n");
595 }
596 rpc.append("</get-config>\n");
597 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800598 rpc.append(ENDPATTERN);
599 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800600 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700601 }
602
603 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800604 public boolean editConfig(String newConfiguration) throws NetconfException {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200605 if (!newConfiguration.endsWith(ENDPATTERN)) {
606 newConfiguration = newConfiguration + ENDPATTERN;
607 }
Andrea Campanella101417d2015-12-11 17:58:07 -0800608 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700609 }
610
611 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700612 public boolean editConfig(DatastoreId netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800613 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800614 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800615 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900616 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800617 rpc.append(MESSAGE_ID_STRING);
618 rpc.append(EQUAL);
619 rpc.append("\"");
620 rpc.append(messageIdInteger.get());
621 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900622 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
623 rpc.append(EDIT_CONFIG_OPEN).append("\n");
624 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300625 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900626 rpc.append(TARGET_CLOSE).append("\n");
627 if (mode != null) {
628 rpc.append(DEFAULT_OPERATION_OPEN);
629 rpc.append(mode);
630 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
631 }
632 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800633 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900634 rpc.append(CONFIG_CLOSE).append("\n");
635 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
636 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800637 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500638 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900639 String reply = sendRequest(rpc.toString());
640 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800641 }
642
643 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700644 public boolean copyConfig(DatastoreId destination,
645 DatastoreId source)
646 throws NetconfException {
647 return bareCopyConfig(destination.asXml(), source.asXml());
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300648 }
649
650 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700651 public boolean copyConfig(DatastoreId netconfTargetConfig,
652 String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800653 throws NetconfException {
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700654 return bareCopyConfig(netconfTargetConfig.asXml(),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200655 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700656 }
657
658 @Override
659 public boolean copyConfig(String netconfTargetConfig,
660 String newConfiguration) throws NetconfException {
661 return bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200662 normalizeCopyConfigParam(newConfiguration));
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700663 }
664
665 /**
666 * Normalize String parameter passed to copy-config API.
667 * <p>
668 * Provided for backward compatibility purpose
669 *
670 * @param input passed to copyConfig API
671 * @return XML likely to be suitable for copy-config source or target
672 */
673 private static CharSequence normalizeCopyConfigParam(String input) {
674 input = input.trim();
675 if (input.startsWith("<url")) {
676 return input;
677 } else if (!input.startsWith("<")) {
678 // assume it is a datastore name
679 return DatastoreId.datastore(input).asXml();
680 } else if (!input.startsWith("<config>")) {
681 return "<config>" + input + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700682 }
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700683 return input;
684 }
685
686 private boolean bareCopyConfig(CharSequence target,
687 CharSequence source)
688 throws NetconfException {
689
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800690 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400691 rpc.append(RPC_OPEN);
692 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700693 rpc.append("<copy-config>");
694 rpc.append("<target>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700695 rpc.append(target);
andreaeb70a942015-10-16 21:34:46 -0700696 rpc.append("</target>");
697 rpc.append("<source>");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700698 rpc.append(source);
andreaeb70a942015-10-16 21:34:46 -0700699 rpc.append("</source>");
700 rpc.append("</copy-config>");
701 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800702 rpc.append(ENDPATTERN);
703 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700704 }
705
706 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700707 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
708 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
andreaeb70a942015-10-16 21:34:46 -0700709 log.warn("Target configuration for delete operation can't be \"running\"",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200710 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700711 return false;
712 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800713 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700714 rpc.append("<rpc>");
715 rpc.append("<delete-config>");
716 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300717 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700718 rpc.append("</target>");
719 rpc.append("</delete-config>");
720 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800721 rpc.append(ENDPATTERN);
722 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700723 }
724
725 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700726 public boolean lock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800727 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700728 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700729 rpc.append("<lock>");
730 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700731 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700732 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700733 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700734 rpc.append("</target>");
735 rpc.append("</lock>");
736 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800737 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700738 String lockReply = sendRequest(rpc.toString());
739 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700740 }
741
742 @Override
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700743 public boolean unlock(DatastoreId configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800744 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700745 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700746 rpc.append("<unlock>");
747 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700748 rpc.append("<");
Yuta HIGUCHI89111d92017-05-04 11:29:17 -0700749 rpc.append(configType.id());
helenyrwu0407c642016-06-09 12:01:30 -0700750 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700751 rpc.append("</target>");
752 rpc.append("</unlock>");
753 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800754 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700755 String unlockReply = sendRequest(rpc.toString());
756 return checkReply(unlockReply);
757 }
758
759 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800760 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700761 return close(false);
762 }
763
Andrea Campanella101417d2015-12-11 17:58:07 -0800764 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700765 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700766 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700767 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700768 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700769 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700770 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700771 }
andreaeb70a942015-10-16 21:34:46 -0700772 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800773 rpc.append(ENDPATTERN);
774 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700775 }
776
777 @Override
778 public String getSessionId() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800779 return sessionID;
andreaeb70a942015-10-16 21:34:46 -0700780 }
781
782 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800783 public Set<String> getDeviceCapabilitiesSet() {
784 return Collections.unmodifiableSet(deviceCapabilities);
785 }
786
andreaeb70a942015-10-16 21:34:46 -0700787 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800788 public void setOnosCapabilities(Iterable<String> capabilities) {
789 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700790 }
791
Andrea Campanella101417d2015-12-11 17:58:07 -0800792 @Override
793 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700794 streamHandler.addDeviceEventListener(listener);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700795 primaryListeners.add(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800796 }
797
798 @Override
Sean Condon54d82432017-07-26 22:27:25 +0100799 public int timeoutConnectSec() {
800 return connectTimeout;
801 }
802
803 @Override
804 public int timeoutReplySec() {
805 return replyTimeout;
806 }
807
808 /**
809 * Idle timeout is not settable on ETZ_SSH - the valuse used is the connect timeout.
810 */
811 @Override
812 public int timeoutIdleSec() {
813 return connectTimeout;
814 }
815
816 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800817 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700818 primaryListeners.remove(listener);
helenyrwu0407c642016-06-09 12:01:30 -0700819 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800820 }
821
822 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700823 if (reply != null) {
824 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900825 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700826 return true;
827 } else if (reply.contains("<ok/>")
828 || (reply.contains("<rpc-error>")
829 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900830 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700831 return true;
832 }
833 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800834 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700835 return false;
836 }
837
Sean Condon7347de92017-07-21 12:17:25 +0100838 protected void publishEvent(NetconfDeviceOutputEvent event) {
839 primaryListeners.forEach(lsnr -> {
840 if (lsnr.isRelevant(event)) {
841 lsnr.event(event);
842 }
843 });
844 }
845
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700846 static class NotificationSession extends NetconfSessionImpl {
847
848 private String notificationFilter;
849
850 NotificationSession(NetconfDeviceInfo deviceInfo)
851 throws NetconfException {
852 super(deviceInfo);
853 }
854
855 @Override
856 protected void startSubscriptionStream(String filterSchema)
857 throws NetconfException {
858
859 notificationFilter = filterSchema;
860 requestSync(createSubscriptionString(filterSchema));
861 }
862
863 @Override
864 public String toString() {
865 return MoreObjects.toStringHelper(getClass())
866 .add("deviceInfo", deviceInfo)
867 .add("sessionID", getSessionId())
868 .add("notificationFilter", notificationFilter)
869 .toString();
870 }
871 }
872
873 /**
874 * Listener attached to child session for notification streaming.
875 *
876 * Forwards all notification event from child session to primary session
877 * listeners.
878 */
879 private final class NotificationForwarder
880 implements NetconfDeviceOutputEventListener {
881
882 @Override
883 public boolean isRelevant(NetconfDeviceOutputEvent event) {
884 return event.type() == Type.DEVICE_NOTIFICATION;
885 }
886
887 @Override
888 public void event(NetconfDeviceOutputEvent event) {
Sean Condon7347de92017-07-21 12:17:25 +0100889 publishEvent(event);
Yuta HIGUCHI0976bc22017-04-20 16:48:20 -0700890 }
891 }
892
Andrea Campanella101417d2015-12-11 17:58:07 -0800893 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700894
Andrea Campanella101417d2015-12-11 17:58:07 -0800895 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200896 public void notify(NetconfDeviceOutputEvent event) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200897 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200898 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200899 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200900 if (!messageId.isPresent()) {
901 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800902 log.error("Device {} sent error reply {}",
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200903 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200904 return;
905 }
906 CompletableFuture<String> completedReply =
907 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900908 if (completedReply != null) {
909 completedReply.complete(event.getMessagePayload());
910 }
andreaeb70a942015-10-16 21:34:46 -0700911 }
912 }
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700913
914 public static class SshNetconfSessionFactory implements NetconfSessionFactory {
915
916 @Override
917 public NetconfSession createNetconfSession(NetconfDeviceInfo netconfDeviceInfo) throws NetconfException {
918 return new NetconfSessionImpl(netconfDeviceInfo);
919 }
920 }
Sean Condon54d82432017-07-26 22:27:25 +0100921}