blob: fc8ac3e2c633f982839fb97a1c4b0a438d27e3d9 [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
andreaeb70a942015-10-16 21:34:46 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070017package org.onosproject.netconf.ctl.impl;
andreaeb70a942015-10-16 21:34:46 -070018
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090019import com.google.common.annotations.Beta;
andreaeb70a942015-10-16 21:34:46 -070020import ch.ethz.ssh2.Connection;
21import ch.ethz.ssh2.Session;
Andrea Campanellac3627842017-04-04 18:06:54 +020022import ch.ethz.ssh2.channel.Channel;
andreaeb70a942015-10-16 21:34:46 -070023import com.google.common.base.Preconditions;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +030024import org.onosproject.netconf.TargetConfig;
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070025import org.onosproject.netconf.FilteringNetconfDeviceOutputEventListener;
andreaeb70a942015-10-16 21:34:46 -070026import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080027import org.onosproject.netconf.NetconfDeviceOutputEvent;
28import org.onosproject.netconf.NetconfDeviceOutputEventListener;
29import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070030import org.onosproject.netconf.NetconfSession;
31import org.slf4j.Logger;
32import org.slf4j.LoggerFactory;
33
andreaeb70a942015-10-16 21:34:46 -070034import java.io.IOException;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020035import java.util.ArrayList;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080036import java.util.Collections;
Aaron Kruglikov72db6422017-02-13 12:16:51 -080037import java.util.LinkedHashSet;
andreaeb70a942015-10-16 21:34:46 -070038import java.util.List;
Andrea Campanella101417d2015-12-11 17:58:07 -080039import java.util.Map;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020040import java.util.Optional;
Aaron Kruglikov72db6422017-02-13 12:16:51 -080041import java.util.Set;
Andrea Campanella101417d2015-12-11 17:58:07 -080042import java.util.concurrent.CompletableFuture;
Sean Condond2c8d472017-02-17 17:09:39 +000043import java.util.concurrent.ConcurrentHashMap;
Andrea Campanellab029b9e2016-01-29 11:05:36 -080044import java.util.concurrent.ExecutionException;
45import java.util.concurrent.TimeUnit;
46import java.util.concurrent.TimeoutException;
Andrea Campanella101417d2015-12-11 17:58:07 -080047import java.util.concurrent.atomic.AtomicInteger;
Sean Condond2c8d472017-02-17 17:09:39 +000048
Aaron Kruglikov72db6422017-02-13 12:16:51 -080049import java.util.regex.Pattern;
50import java.util.regex.Matcher;
Andrea Campanella101417d2015-12-11 17:58:07 -080051
andreaeb70a942015-10-16 21:34:46 -070052/**
53 * Implementation of a NETCONF session to talk to a device.
54 */
55public class NetconfSessionImpl implements NetconfSession {
56
Andrea Campanella101417d2015-12-11 17:58:07 -080057 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070058 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080059
Andrea Campanella101417d2015-12-11 17:58:07 -080060 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080061 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080062 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080063 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080064 private static final String END_OF_RPC_OPEN_TAG = "\">";
65 private static final String EQUAL = "=";
66 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090067 private static final String RPC_OPEN = "<rpc ";
68 private static final String RPC_CLOSE = "</rpc>";
69 private static final String GET_OPEN = "<get>";
70 private static final String GET_CLOSE = "</get>";
71 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
72 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090073 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
74 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090075 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
76 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090077 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
78 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
79 private static final String TARGET_OPEN = "<target>";
80 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000081 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 +090082 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080083 private static final String XML_HEADER =
84 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090085 private static final String NETCONF_BASE_NAMESPACE =
86 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
87 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
88 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090089 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
90 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -070091
Aaron Kruglikov72db6422017-02-13 12:16:51 -080092 private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
Sean Condond2c8d472017-02-17 17:09:39 +000093
Aaron Kruglikov72db6422017-02-13 12:16:51 -080094 private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
95 private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
96
97 private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
98 private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
99
100 private String sessionID;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800101 private final AtomicInteger messageIdInteger = new AtomicInteger(0);
andreaeb70a942015-10-16 21:34:46 -0700102 private Connection netconfConnection;
103 private NetconfDeviceInfo deviceInfo;
104 private Session sshSession;
105 private boolean connectionActive;
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800106 private Iterable<String> onosCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800107 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800108
109 /* NOTE: the "serverHelloResponseOld" is deprecated in 1.10.0 and should eventually be removed */
110 @Deprecated
111 private String serverHelloResponseOld;
112 private final Set<String> deviceCapabilities = new LinkedHashSet<>();
helenyrwu0407c642016-06-09 12:01:30 -0700113 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -0800114 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200115 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -0700116 private boolean subscriptionConnected = false;
Andrea Campanellac3627842017-04-04 18:06:54 +0200117 private String notificationFilterSchema = null;
andreaeb70a942015-10-16 21:34:46 -0700118
119
Andrea Campanella101417d2015-12-11 17:58:07 -0800120 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700121 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900122 this.netconfConnection = null;
123 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700124 connectionActive = false;
Sean Condond2c8d472017-02-17 17:09:39 +0000125 replies = new ConcurrentHashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200126 errorReplies = new ArrayList<>();
andreaeb70a942015-10-16 21:34:46 -0700127 startConnection();
128 }
129
Andrea Campanella101417d2015-12-11 17:58:07 -0800130 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700131 if (!connectionActive) {
132 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000133 int connectTimeout = NetconfControllerImpl.netconfConnectTimeout;
134
Andrea Campanella101417d2015-12-11 17:58:07 -0800135 try {
Sean Condon334ad692016-12-13 17:56:56 +0000136 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800137 } catch (IOException e) {
Yuta HIGUCHI0184a7b2017-03-31 13:13:58 -0700138 throw new NetconfException("Cannot open a connection with device " + deviceInfo, e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800139 }
andreaeb70a942015-10-16 21:34:46 -0700140 boolean isAuthenticated;
141 try {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800142 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
143 log.debug("Authenticating with key file to device {} with username {}",
144 deviceInfo.getDeviceId(), deviceInfo.name());
145 isAuthenticated = netconfConnection.authenticateWithPublicKey(
146 deviceInfo.name(), deviceInfo.getKeyFile(),
147 deviceInfo.password().equals("") ? null : deviceInfo.password());
148 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600149 log.debug("Authenticating with key to device {} with username {}",
Andrea Campanellac3627842017-04-04 18:06:54 +0200150 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700151 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600152 deviceInfo.name(), deviceInfo.getKey(),
153 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700154 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600155 log.debug("Authenticating to device {} with username {} with password",
Andrea Campanella50d25212016-02-26 13:06:23 -0800156 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700157 isAuthenticated = netconfConnection.authenticateWithPassword(
158 deviceInfo.name(), deviceInfo.password());
159 }
160 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500161 log.error("Authentication connection to device {} failed",
162 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800163 throw new NetconfException("Authentication connection to device " +
164 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700165 }
166
167 connectionActive = true;
168 Preconditions.checkArgument(isAuthenticated,
Andrea Campanellad264b492016-03-01 09:46:06 -0800169 "Authentication to device %s with username " +
170 "%s failed",
Andrea Campanella50d25212016-02-26 13:06:23 -0800171 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700172 startSshSession();
173 }
174 }
175
Andrea Campanella101417d2015-12-11 17:58:07 -0800176 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700177 try {
178 sshSession = netconfConnection.openSession();
179 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700180 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
181 sshSession.getStderr(), deviceInfo,
Sean Condond2c8d472017-02-17 17:09:39 +0000182 new NetconfSessionDelegateImpl(),
183 replies);
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700184 this.addDeviceOutputListener(new FilteringNetconfDeviceOutputEventListener(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700185 sendHello();
186 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200187 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800188 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
189 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700190 }
191 }
192
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900193
194 @Beta
195 private void startSubscriptionConnection(String filterSchema) throws NetconfException {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800196 if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
helenyrwu0407c642016-06-09 12:01:30 -0700197 throw new NetconfException("Device" + deviceInfo + "does not support interleave");
198 }
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900199 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700200 if (!checkReply(reply)) {
201 throw new NetconfException("Subscription not successful with device "
202 + deviceInfo + " with reply " + reply);
203 }
204 subscriptionConnected = true;
205 }
206
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900207 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700208 public void startSubscription() throws NetconfException {
209 if (!subscriptionConnected) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900210 startSubscriptionConnection(null);
helenyrwu0407c642016-06-09 12:01:30 -0700211 }
212 streamHandler.setEnableNotifications(true);
213 }
214
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900215 @Beta
216 @Override
217 public void startSubscription(String filterSchema) throws NetconfException {
218 if (!subscriptionConnected) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200219 notificationFilterSchema = filterSchema;
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900220 startSubscriptionConnection(filterSchema);
221 }
222 streamHandler.setEnableNotifications(true);
223 }
224
225 @Beta
226 private String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700227 StringBuilder subscriptionbuffer = new StringBuilder();
228 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
229 subscriptionbuffer.append(" <create-subscription\n");
230 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900231 // FIXME Only subtree filtering supported at the moment.
232 if (filterSchema != null) {
233 subscriptionbuffer.append(" ");
234 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
235 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
236 subscriptionbuffer.append(" ");
237 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
238 }
helenyrwu0407c642016-06-09 12:01:30 -0700239 subscriptionbuffer.append(" </create-subscription>\n");
240 subscriptionbuffer.append("</rpc>\n");
241 subscriptionbuffer.append(ENDPATTERN);
242 return subscriptionbuffer.toString();
243 }
244
245 @Override
246 public void endSubscription() throws NetconfException {
247 if (subscriptionConnected) {
248 streamHandler.setEnableNotifications(false);
249 } else {
250 throw new NetconfException("Subscription does not exist.");
251 }
252 }
253
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800254 private void sendHello() throws NetconfException {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800255 serverHelloResponseOld = sendRequest(createHelloString());
256 Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
257 while (capabilityMatcher.find()) {
258 deviceCapabilities.add(capabilityMatcher.group(1));
259 }
260 sessionID = String.valueOf(-1);
261 Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
262 if (sessionIDMatcher.find()) {
263 sessionID = sessionIDMatcher.group(1);
264 } else {
265 throw new NetconfException("Missing SessionID in server hello " +
266 "reponse.");
267 }
268
andreaeb70a942015-10-16 21:34:46 -0700269 }
270
271 private String createHelloString() {
272 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800273 hellobuffer.append(XML_HEADER);
274 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700275 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
276 hellobuffer.append(" <capabilities>\n");
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800277 onosCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800278 cap -> hellobuffer.append(" <capability>")
279 .append(cap)
280 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700281 hellobuffer.append(" </capabilities>\n");
282 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800283 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700284 return hellobuffer.toString();
285
286 }
287
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700288 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200289 public void checkAndReestablish() throws NetconfException {
290 if (sshSession.getState() != Channel.STATE_OPEN) {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800291 try {
Andrea Campanellac3627842017-04-04 18:06:54 +0200292 log.debug("Trying to reopen the Sesion with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800293 startSshSession();
Andrea Campanellac3627842017-04-04 18:06:54 +0200294 } catch (IOException | IllegalStateException e) {
295 log.debug("Trying to reopen the Connection with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800296 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200297 connectionActive = false;
298 replies.clear();
299 messageIdInteger.set(0);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800300 startConnection();
Andrea Campanellac3627842017-04-04 18:06:54 +0200301 if (subscriptionConnected) {
302 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
303 subscriptionConnected = false;
304 startSubscription(notificationFilterSchema);
305 }
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800306 } catch (IOException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200307 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800308 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800309 }
310 }
311 }
312 }
313
andreaeb70a942015-10-16 21:34:46 -0700314 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800315 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800316 if (!request.contains(ENDPATTERN)) {
317 request = request + NEW_LINE + ENDPATTERN;
318 }
319 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300320 checkReply(reply);
321 return reply;
andreaeb70a942015-10-16 21:34:46 -0700322 }
323
324 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000325 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800326 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000327 return streamHandler.sendMessage(request);
328 }
329
330 private CompletableFuture<String> request(String request, int messageId) {
331 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800332 }
333
334 private String sendRequest(String request) throws NetconfException {
Andrea Campanellac3627842017-04-04 18:06:54 +0200335 checkAndReestablish();
Sean Condond2c8d472017-02-17 17:09:39 +0000336 final int messageId = messageIdInteger.getAndIncrement();
337 request = formatRequestMessageId(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800338 request = formatXmlHeader(request);
Sean Condond2c8d472017-02-17 17:09:39 +0000339 CompletableFuture<String> futureReply = request(request, messageId);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300340 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800341 String rp;
342 try {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300343 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000344 replies.remove(messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800345 } catch (InterruptedException | ExecutionException | TimeoutException e) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200346 throw new NetconfException("No matching reply for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800347 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800348 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800349 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800350 }
351
Sean Condond2c8d472017-02-17 17:09:39 +0000352 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800353 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000354 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800355 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Sean Condond2c8d472017-02-17 17:09:39 +0000356 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800357 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
358 //FIXME find out a better way to enforce the presence of message-id
359 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000360 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800361 }
362 return request;
363 }
364
365 private String formatXmlHeader(String request) {
366 if (!request.contains(XML_HEADER)) {
367 //FIXME if application provieds his own XML header of different type there is a clash
368 request = XML_HEADER + "\n" + request;
369 }
370 return request;
371 }
372
Andrea Campanella101417d2015-12-11 17:58:07 -0800373 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900374 public String doWrappedRpc(String request) throws NetconfException {
375 StringBuilder rpc = new StringBuilder(XML_HEADER);
376 rpc.append(RPC_OPEN);
377 rpc.append(MESSAGE_ID_STRING);
378 rpc.append(EQUAL);
379 rpc.append("\"");
380 rpc.append(messageIdInteger.get());
381 rpc.append("\" ");
382 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
383 rpc.append(request);
384 rpc.append(RPC_CLOSE).append(NEW_LINE);
385 rpc.append(ENDPATTERN);
386 String reply = sendRequest(rpc.toString());
387 checkReply(reply);
388 return reply;
389 }
390
391 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800392 public String get(String request) throws NetconfException {
393 return requestSync(request);
394 }
395
396 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900397 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
398 StringBuilder rpc = new StringBuilder(XML_HEADER);
399 rpc.append(RPC_OPEN);
400 rpc.append(MESSAGE_ID_STRING);
401 rpc.append(EQUAL);
402 rpc.append("\"");
403 rpc.append(messageIdInteger.get());
404 rpc.append("\" ");
405 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
406 rpc.append(GET_OPEN).append(NEW_LINE);
407 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900408 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900409 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900410 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900411 }
412 if (withDefaultsMode != null) {
413 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
414 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
415 }
416 rpc.append(GET_CLOSE).append(NEW_LINE);
417 rpc.append(RPC_CLOSE).append(NEW_LINE);
418 rpc.append(ENDPATTERN);
419 String reply = sendRequest(rpc.toString());
420 checkReply(reply);
421 return reply;
422 }
423
424 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300425 public String getConfig(TargetConfig netconfTargetConfig) throws NetconfException {
426 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700427 }
428
429 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300430 public String getConfig(String netconfTargetConfig) throws NetconfException {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700431 return getConfig(TargetConfig.toTargetConfig(netconfTargetConfig));
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300432 }
433
434 @Override
435 public String getConfig(String netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700436 return getConfig(TargetConfig.toTargetConfig(netconfTargetConfig), configurationFilterSchema);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300437 }
438
439 @Override
440 public String getConfig(TargetConfig netconfTargetConfig, String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800441 StringBuilder rpc = new StringBuilder(XML_HEADER);
442 rpc.append("<rpc ");
443 rpc.append(MESSAGE_ID_STRING);
444 rpc.append(EQUAL);
445 rpc.append("\"");
446 rpc.append(messageIdInteger.get());
447 rpc.append("\" ");
448 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700449 rpc.append("<get-config>\n");
450 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300451 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700452 rpc.append("</source>");
453 if (configurationSchema != null) {
454 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800455 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700456 rpc.append("</filter>\n");
457 }
458 rpc.append("</get-config>\n");
459 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800460 rpc.append(ENDPATTERN);
461 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800462 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700463 }
464
465 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800466 public boolean editConfig(String newConfiguration) throws NetconfException {
467 newConfiguration = newConfiguration + ENDPATTERN;
468 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700469 }
470
471 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300472 public boolean editConfig(String netconfTargetConfig, String mode, String newConfiguration)
473 throws NetconfException {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700474 return editConfig(TargetConfig.toTargetConfig(netconfTargetConfig), mode, newConfiguration);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300475 }
476
477 @Override
478 public boolean editConfig(TargetConfig netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800479 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800480 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800481 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900482 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800483 rpc.append(MESSAGE_ID_STRING);
484 rpc.append(EQUAL);
485 rpc.append("\"");
486 rpc.append(messageIdInteger.get());
487 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900488 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
489 rpc.append(EDIT_CONFIG_OPEN).append("\n");
490 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300491 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900492 rpc.append(TARGET_CLOSE).append("\n");
493 if (mode != null) {
494 rpc.append(DEFAULT_OPERATION_OPEN);
495 rpc.append(mode);
496 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
497 }
498 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800499 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900500 rpc.append(CONFIG_CLOSE).append("\n");
501 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
502 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800503 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500504 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900505 String reply = sendRequest(rpc.toString());
506 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800507 }
508
509 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300510 public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700511 return copyConfig(TargetConfig.toTargetConfig(netconfTargetConfig), newConfiguration);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300512 }
513
514 @Override
515 public boolean copyConfig(TargetConfig netconfTargetConfig, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800516 throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700517 newConfiguration = newConfiguration.trim();
yoonseona1957552016-11-02 15:53:07 -0400518 if (!newConfiguration.startsWith("<config>")) {
519 newConfiguration = "<config>" + newConfiguration
520 + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700521 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800522 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400523 rpc.append(RPC_OPEN);
524 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700525 rpc.append("<copy-config>");
526 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300527 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700528 rpc.append("</target>");
529 rpc.append("<source>");
yoonseon25e54fc2016-11-02 13:07:43 -0400530 rpc.append(newConfiguration);
andreaeb70a942015-10-16 21:34:46 -0700531 rpc.append("</source>");
532 rpc.append("</copy-config>");
533 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800534 rpc.append(ENDPATTERN);
535 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700536 }
537
538 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300539 public boolean deleteConfig(String netconfTargetConfig) throws NetconfException {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700540 return deleteConfig(TargetConfig.toTargetConfig(netconfTargetConfig));
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300541 }
542
543 @Override
544 public boolean deleteConfig(TargetConfig netconfTargetConfig) throws NetconfException {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700545 if (netconfTargetConfig.equals(TargetConfig.RUNNING)) {
andreaeb70a942015-10-16 21:34:46 -0700546 log.warn("Target configuration for delete operation can't be \"running\"",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300547 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700548 return false;
549 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800550 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700551 rpc.append("<rpc>");
552 rpc.append("<delete-config>");
553 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300554 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700555 rpc.append("</target>");
556 rpc.append("</delete-config>");
557 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800558 rpc.append(ENDPATTERN);
559 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700560 }
561
562 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700563 public boolean lock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800564 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700565 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700566 rpc.append("<lock>");
567 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700568 rpc.append("<");
569 rpc.append(configType);
570 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700571 rpc.append("</target>");
572 rpc.append("</lock>");
573 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800574 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700575 String lockReply = sendRequest(rpc.toString());
576 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700577 }
578
579 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700580 public boolean unlock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800581 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700582 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700583 rpc.append("<unlock>");
584 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700585 rpc.append("<");
586 rpc.append(configType);
587 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700588 rpc.append("</target>");
589 rpc.append("</unlock>");
590 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800591 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700592 String unlockReply = sendRequest(rpc.toString());
593 return checkReply(unlockReply);
594 }
595
596 @Override
597 public boolean lock() throws NetconfException {
598 return lock("running");
599 }
600
601 @Override
602 public boolean unlock() throws NetconfException {
603 return unlock("running");
andreaeb70a942015-10-16 21:34:46 -0700604 }
605
606 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800607 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700608 return close(false);
609 }
610
Andrea Campanella101417d2015-12-11 17:58:07 -0800611 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700612 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700613 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700614 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700615 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700616 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700617 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700618 }
andreaeb70a942015-10-16 21:34:46 -0700619 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800620 rpc.append(ENDPATTERN);
621 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700622 }
623
624 @Override
625 public String getSessionId() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800626 return sessionID;
andreaeb70a942015-10-16 21:34:46 -0700627 }
628
629 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800630 public Set<String> getDeviceCapabilitiesSet() {
631 return Collections.unmodifiableSet(deviceCapabilities);
632 }
633
634 @Deprecated
635 @Override
andreaeb70a942015-10-16 21:34:46 -0700636 public String getServerCapabilities() {
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800637 return serverHelloResponseOld;
638 }
639
640 @Deprecated
641 @Override
642 public void setDeviceCapabilities(List<String> capabilities) {
643 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700644 }
645
646 @Override
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800647 public void setOnosCapabilities(Iterable<String> capabilities) {
648 onosCapabilities = capabilities;
andreaeb70a942015-10-16 21:34:46 -0700649 }
650
Aaron Kruglikov72db6422017-02-13 12:16:51 -0800651
Andrea Campanella101417d2015-12-11 17:58:07 -0800652 @Override
653 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700654 streamHandler.addDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800655 }
656
657 @Override
658 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700659 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800660 }
661
662 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700663 if (reply != null) {
664 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900665 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700666 return true;
667 } else if (reply.contains("<ok/>")
668 || (reply.contains("<rpc-error>")
669 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900670 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700671 return true;
672 }
673 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800674 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700675 return false;
676 }
677
Andrea Campanella101417d2015-12-11 17:58:07 -0800678 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700679
Andrea Campanella101417d2015-12-11 17:58:07 -0800680 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200681 public void notify(NetconfDeviceOutputEvent event) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200682 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200683 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
684 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200685 if (!messageId.isPresent()) {
686 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800687 log.error("Device {} sent error reply {}",
688 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200689 return;
690 }
691 CompletableFuture<String> completedReply =
692 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900693 if (completedReply != null) {
694 completedReply.complete(event.getMessagePayload());
695 }
andreaeb70a942015-10-16 21:34:46 -0700696 }
697 }
andreaeb70a942015-10-16 21:34:46 -0700698}