blob: 07afac81a191dcb7350bd1e0097e7bdcb7d9f85c [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
17package org.onosproject.netconf.ctl;
18
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;
andreaeb70a942015-10-16 21:34:46 -070025import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080026import org.onosproject.netconf.NetconfDeviceOutputEvent;
27import org.onosproject.netconf.NetconfDeviceOutputEventListener;
28import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070029import org.onosproject.netconf.NetconfSession;
30import org.slf4j.Logger;
31import org.slf4j.LoggerFactory;
32
andreaeb70a942015-10-16 21:34:46 -070033import java.io.IOException;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020034import java.util.ArrayList;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080035import java.util.Collections;
andreaeb70a942015-10-16 21:34:46 -070036import java.util.List;
Andrea Campanella101417d2015-12-11 17:58:07 -080037import java.util.Map;
Andreas Papazoisd4712e22016-02-10 15:59:55 +020038import java.util.Optional;
Andrea Campanella101417d2015-12-11 17:58:07 -080039import java.util.concurrent.CompletableFuture;
Sean Condond2c8d472017-02-17 17:09:39 +000040import java.util.concurrent.ConcurrentHashMap;
Andrea Campanellab029b9e2016-01-29 11:05:36 -080041import java.util.concurrent.ExecutionException;
42import java.util.concurrent.TimeUnit;
43import java.util.concurrent.TimeoutException;
Andrea Campanella101417d2015-12-11 17:58:07 -080044import java.util.concurrent.atomic.AtomicInteger;
Sean Condond2c8d472017-02-17 17:09:39 +000045import java.util.regex.Pattern;
46
Andrea Campanella101417d2015-12-11 17:58:07 -080047
andreaeb70a942015-10-16 21:34:46 -070048/**
49 * Implementation of a NETCONF session to talk to a device.
50 */
51public class NetconfSessionImpl implements NetconfSession {
52
Andrea Campanella101417d2015-12-11 17:58:07 -080053 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070054 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080055
Andrea Campanella101417d2015-12-11 17:58:07 -080056 private static final String ENDPATTERN = "]]>]]>";
Andrea Campanella101417d2015-12-11 17:58:07 -080057 private static final String MESSAGE_ID_STRING = "message-id";
Andrea Campanella1311ea02016-03-04 17:51:25 -080058 private static final String HELLO = "<hello";
Andrea Campanella101417d2015-12-11 17:58:07 -080059 private static final String NEW_LINE = "\n";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080060 private static final String END_OF_RPC_OPEN_TAG = "\">";
61 private static final String EQUAL = "=";
62 private static final String NUMBER_BETWEEN_QUOTES_MATCHER = "\"+([0-9]+)+\"";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090063 private static final String RPC_OPEN = "<rpc ";
64 private static final String RPC_CLOSE = "</rpc>";
65 private static final String GET_OPEN = "<get>";
66 private static final String GET_CLOSE = "</get>";
67 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
68 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090069 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
70 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090071 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
72 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
Akihiro Yamanouchid4912842016-07-01 10:38:46 +090073 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
74 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
75 private static final String TARGET_OPEN = "<target>";
76 private static final String TARGET_CLOSE = "</target>";
Sean Condonb0720e72017-01-10 12:29:02 +000077 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 +090078 private static final String CONFIG_CLOSE = "</config>";
Andrea Campanellab029b9e2016-01-29 11:05:36 -080079 private static final String XML_HEADER =
80 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +090081 private static final String NETCONF_BASE_NAMESPACE =
82 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
83 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
84 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
Akihiro Yamanouchi45122222016-07-15 13:13:11 +090085 private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
86 "<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
andreaeb70a942015-10-16 21:34:46 -070087
Sean Condond2c8d472017-02-17 17:09:39 +000088 private static Pattern msgIdPattern = Pattern.compile("(message-id=\"[0-9]+\")");
89
Andrea Campanellab029b9e2016-01-29 11:05:36 -080090 private final AtomicInteger messageIdInteger = new AtomicInteger(0);
andreaeb70a942015-10-16 21:34:46 -070091 private Connection netconfConnection;
92 private NetconfDeviceInfo deviceInfo;
93 private Session sshSession;
94 private boolean connectionActive;
andreaeb70a942015-10-16 21:34:46 -070095 private List<String> deviceCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -080096 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
andreaeb70a942015-10-16 21:34:46 -070097 private String serverCapabilities;
helenyrwu0407c642016-06-09 12:01:30 -070098 private NetconfStreamHandler streamHandler;
Andrea Campanella101417d2015-12-11 17:58:07 -080099 private Map<Integer, CompletableFuture<String>> replies;
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200100 private List<String> errorReplies;
helenyrwu0407c642016-06-09 12:01:30 -0700101 private boolean subscriptionConnected = false;
Andrea Campanellac3627842017-04-04 18:06:54 +0200102 private String notificationFilterSchema = null;
andreaeb70a942015-10-16 21:34:46 -0700103
104
Andrea Campanella101417d2015-12-11 17:58:07 -0800105 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700106 this.deviceInfo = deviceInfo;
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900107 this.netconfConnection = null;
108 this.sshSession = null;
andreaeb70a942015-10-16 21:34:46 -0700109 connectionActive = false;
Sean Condond2c8d472017-02-17 17:09:39 +0000110 replies = new ConcurrentHashMap<>();
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200111 errorReplies = new ArrayList<>();
andreaeb70a942015-10-16 21:34:46 -0700112 startConnection();
113 }
114
Andrea Campanella101417d2015-12-11 17:58:07 -0800115 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700116 if (!connectionActive) {
117 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Sean Condon334ad692016-12-13 17:56:56 +0000118 int connectTimeout = NetconfControllerImpl.netconfConnectTimeout;
119
Andrea Campanella101417d2015-12-11 17:58:07 -0800120 try {
Sean Condon334ad692016-12-13 17:56:56 +0000121 netconfConnection.connect(null, 1000 * connectTimeout, 1000 * connectTimeout);
Andrea Campanella101417d2015-12-11 17:58:07 -0800122 } catch (IOException e) {
Yuta HIGUCHI0184a7b2017-03-31 13:13:58 -0700123 throw new NetconfException("Cannot open a connection with device " + deviceInfo, e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800124 }
andreaeb70a942015-10-16 21:34:46 -0700125 boolean isAuthenticated;
126 try {
Andrea Campanellae7006dc2017-02-15 16:04:09 -0800127 if (deviceInfo.getKeyFile() != null && deviceInfo.getKeyFile().canRead()) {
128 log.debug("Authenticating with key file to device {} with username {}",
129 deviceInfo.getDeviceId(), deviceInfo.name());
130 isAuthenticated = netconfConnection.authenticateWithPublicKey(
131 deviceInfo.name(), deviceInfo.getKeyFile(),
132 deviceInfo.password().equals("") ? null : deviceInfo.password());
133 } else if (deviceInfo.getKey() != null) {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600134 log.debug("Authenticating with key to device {} with username {}",
Andrea Campanellac3627842017-04-04 18:06:54 +0200135 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700136 isAuthenticated = netconfConnection.authenticateWithPublicKey(
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600137 deviceInfo.name(), deviceInfo.getKey(),
138 deviceInfo.password().equals("") ? null : deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700139 } else {
Himanshu Ranjan7c2ee3c2017-02-13 05:10:08 -0600140 log.debug("Authenticating to device {} with username {} with password",
Andrea Campanella50d25212016-02-26 13:06:23 -0800141 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700142 isAuthenticated = netconfConnection.authenticateWithPassword(
143 deviceInfo.name(), deviceInfo.password());
144 }
145 } catch (IOException e) {
Ryan Goulding69524392016-12-03 13:21:20 -0500146 log.error("Authentication connection to device {} failed",
147 deviceInfo.getDeviceId(), e);
Andrea Campanella101417d2015-12-11 17:58:07 -0800148 throw new NetconfException("Authentication connection to device " +
149 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700150 }
151
152 connectionActive = true;
153 Preconditions.checkArgument(isAuthenticated,
Andrea Campanellad264b492016-03-01 09:46:06 -0800154 "Authentication to device %s with username " +
155 "%s failed",
Andrea Campanella50d25212016-02-26 13:06:23 -0800156 deviceInfo.getDeviceId(), deviceInfo.name());
andreaeb70a942015-10-16 21:34:46 -0700157 startSshSession();
158 }
159 }
160
Andrea Campanella101417d2015-12-11 17:58:07 -0800161 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700162 try {
163 sshSession = netconfConnection.openSession();
164 sshSession.startSubSystem("netconf");
helenyrwu0407c642016-06-09 12:01:30 -0700165 streamHandler = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
166 sshSession.getStderr(), deviceInfo,
Sean Condond2c8d472017-02-17 17:09:39 +0000167 new NetconfSessionDelegateImpl(),
168 replies);
Andrea Campanella101417d2015-12-11 17:58:07 -0800169 this.addDeviceOutputListener(new NetconfDeviceOutputEventListenerImpl(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700170 sendHello();
171 } catch (IOException e) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200172 log.error("Failed to create ch.ethz.ssh2.Session session {} ", e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800173 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
174 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700175 }
176 }
177
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900178
179 @Beta
180 private void startSubscriptionConnection(String filterSchema) throws NetconfException {
helenyrwu0407c642016-06-09 12:01:30 -0700181 if (!serverCapabilities.contains("interleave")) {
182 throw new NetconfException("Device" + deviceInfo + "does not support interleave");
183 }
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900184 String reply = sendRequest(createSubscriptionString(filterSchema));
helenyrwu0407c642016-06-09 12:01:30 -0700185 if (!checkReply(reply)) {
186 throw new NetconfException("Subscription not successful with device "
187 + deviceInfo + " with reply " + reply);
188 }
189 subscriptionConnected = true;
190 }
191
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900192 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700193 public void startSubscription() throws NetconfException {
194 if (!subscriptionConnected) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900195 startSubscriptionConnection(null);
helenyrwu0407c642016-06-09 12:01:30 -0700196 }
197 streamHandler.setEnableNotifications(true);
198 }
199
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900200 @Beta
201 @Override
202 public void startSubscription(String filterSchema) throws NetconfException {
203 if (!subscriptionConnected) {
Andrea Campanellac3627842017-04-04 18:06:54 +0200204 notificationFilterSchema = filterSchema;
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900205 startSubscriptionConnection(filterSchema);
206 }
207 streamHandler.setEnableNotifications(true);
208 }
209
210 @Beta
211 private String createSubscriptionString(String filterSchema) {
helenyrwu0407c642016-06-09 12:01:30 -0700212 StringBuilder subscriptionbuffer = new StringBuilder();
213 subscriptionbuffer.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
214 subscriptionbuffer.append(" <create-subscription\n");
215 subscriptionbuffer.append("xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n");
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900216 // FIXME Only subtree filtering supported at the moment.
217 if (filterSchema != null) {
218 subscriptionbuffer.append(" ");
219 subscriptionbuffer.append(SUBSCRIPTION_SUBTREE_FILTER_OPEN).append(NEW_LINE);
220 subscriptionbuffer.append(filterSchema).append(NEW_LINE);
221 subscriptionbuffer.append(" ");
222 subscriptionbuffer.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
223 }
helenyrwu0407c642016-06-09 12:01:30 -0700224 subscriptionbuffer.append(" </create-subscription>\n");
225 subscriptionbuffer.append("</rpc>\n");
226 subscriptionbuffer.append(ENDPATTERN);
227 return subscriptionbuffer.toString();
228 }
229
230 @Override
231 public void endSubscription() throws NetconfException {
232 if (subscriptionConnected) {
233 streamHandler.setEnableNotifications(false);
234 } else {
235 throw new NetconfException("Subscription does not exist.");
236 }
237 }
238
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800239 private void sendHello() throws NetconfException {
Andrea Campanella101417d2015-12-11 17:58:07 -0800240 serverCapabilities = sendRequest(createHelloString());
andreaeb70a942015-10-16 21:34:46 -0700241 }
242
243 private String createHelloString() {
244 StringBuilder hellobuffer = new StringBuilder();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800245 hellobuffer.append(XML_HEADER);
246 hellobuffer.append("\n");
andreaeb70a942015-10-16 21:34:46 -0700247 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
248 hellobuffer.append(" <capabilities>\n");
249 deviceCapabilities.forEach(
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800250 cap -> hellobuffer.append(" <capability>")
251 .append(cap)
252 .append("</capability>\n"));
andreaeb70a942015-10-16 21:34:46 -0700253 hellobuffer.append(" </capabilities>\n");
254 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800255 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700256 return hellobuffer.toString();
257
258 }
259
Andrea Campanellac3627842017-04-04 18:06:54 +0200260 public void checkAndReestablish() throws NetconfException {
261 if (sshSession.getState() != Channel.STATE_OPEN) {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800262 try {
Andrea Campanellac3627842017-04-04 18:06:54 +0200263 log.debug("Trying to reopen the Sesion with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800264 startSshSession();
Andrea Campanellac3627842017-04-04 18:06:54 +0200265 } catch (IOException | IllegalStateException e) {
266 log.debug("Trying to reopen the Connection with {}", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800267 try {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200268 connectionActive = false;
269 replies.clear();
270 messageIdInteger.set(0);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800271 startConnection();
Andrea Campanellac3627842017-04-04 18:06:54 +0200272 if (subscriptionConnected) {
273 log.debug("Restarting subscription with {}", deviceInfo.getDeviceId());
274 subscriptionConnected = false;
275 startSubscription(notificationFilterSchema);
276 }
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800277 } catch (IOException e2) {
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200278 log.error("No connection {} for device {}", netconfConnection, e.getMessage());
Andrea Campanella101417d2015-12-11 17:58:07 -0800279 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800280 }
281 }
282 }
283 }
284
andreaeb70a942015-10-16 21:34:46 -0700285 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800286 public String requestSync(String request) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800287 if (!request.contains(ENDPATTERN)) {
288 request = request + NEW_LINE + ENDPATTERN;
289 }
290 String reply = sendRequest(request);
Andreas Papazois2e557be2016-06-04 15:39:56 +0300291 checkReply(reply);
292 return reply;
andreaeb70a942015-10-16 21:34:46 -0700293 }
294
295 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000296 @Deprecated
Andrea Campanella101417d2015-12-11 17:58:07 -0800297 public CompletableFuture<String> request(String request) {
Sean Condond2c8d472017-02-17 17:09:39 +0000298 return streamHandler.sendMessage(request);
299 }
300
301 private CompletableFuture<String> request(String request, int messageId) {
302 return streamHandler.sendMessage(request, messageId);
Andrea Campanella101417d2015-12-11 17:58:07 -0800303 }
304
305 private String sendRequest(String request) throws NetconfException {
Andrea Campanellac3627842017-04-04 18:06:54 +0200306 checkAndReestablish();
Sean Condond2c8d472017-02-17 17:09:39 +0000307 final int messageId = messageIdInteger.getAndIncrement();
308 request = formatRequestMessageId(request, messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800309 request = formatXmlHeader(request);
Sean Condond2c8d472017-02-17 17:09:39 +0000310 CompletableFuture<String> futureReply = request(request, messageId);
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300311 int replyTimeout = NetconfControllerImpl.netconfReplyTimeout;
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800312 String rp;
313 try {
Andreas Papazois4752cfa2016-04-25 14:52:12 +0300314 rp = futureReply.get(replyTimeout, TimeUnit.SECONDS);
Sean Condond2c8d472017-02-17 17:09:39 +0000315 replies.remove(messageId);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800316 } catch (InterruptedException | ExecutionException | TimeoutException e) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200317 throw new NetconfException("No matching reply for request " + request, e);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800318 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800319 log.debug("Result {} from request {} to device {}", rp, request, deviceInfo);
Andrea Campanella50d25212016-02-26 13:06:23 -0800320 return rp.trim();
Andrea Campanella101417d2015-12-11 17:58:07 -0800321 }
322
Sean Condond2c8d472017-02-17 17:09:39 +0000323 private String formatRequestMessageId(String request, int messageId) {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800324 if (request.contains(MESSAGE_ID_STRING)) {
Sean Condond2c8d472017-02-17 17:09:39 +0000325 //FIXME if application provides his own counting of messages this fails that count
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800326 request = request.replaceFirst(MESSAGE_ID_STRING + EQUAL + NUMBER_BETWEEN_QUOTES_MATCHER,
Sean Condond2c8d472017-02-17 17:09:39 +0000327 MESSAGE_ID_STRING + EQUAL + "\"" + messageId + "\"");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800328 } else if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
329 //FIXME find out a better way to enforce the presence of message-id
330 request = request.replaceFirst(END_OF_RPC_OPEN_TAG, "\" " + MESSAGE_ID_STRING + EQUAL + "\""
Sean Condond2c8d472017-02-17 17:09:39 +0000331 + messageId + "\"" + ">");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800332 }
333 return request;
334 }
335
336 private String formatXmlHeader(String request) {
337 if (!request.contains(XML_HEADER)) {
338 //FIXME if application provieds his own XML header of different type there is a clash
339 request = XML_HEADER + "\n" + request;
340 }
341 return request;
342 }
343
Andrea Campanella101417d2015-12-11 17:58:07 -0800344 @Override
Akihiro Yamanouchi8d3a9d32016-07-12 11:41:44 +0900345 public String doWrappedRpc(String request) throws NetconfException {
346 StringBuilder rpc = new StringBuilder(XML_HEADER);
347 rpc.append(RPC_OPEN);
348 rpc.append(MESSAGE_ID_STRING);
349 rpc.append(EQUAL);
350 rpc.append("\"");
351 rpc.append(messageIdInteger.get());
352 rpc.append("\" ");
353 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
354 rpc.append(request);
355 rpc.append(RPC_CLOSE).append(NEW_LINE);
356 rpc.append(ENDPATTERN);
357 String reply = sendRequest(rpc.toString());
358 checkReply(reply);
359 return reply;
360 }
361
362 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800363 public String get(String request) throws NetconfException {
364 return requestSync(request);
365 }
366
367 @Override
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900368 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
369 StringBuilder rpc = new StringBuilder(XML_HEADER);
370 rpc.append(RPC_OPEN);
371 rpc.append(MESSAGE_ID_STRING);
372 rpc.append(EQUAL);
373 rpc.append("\"");
374 rpc.append(messageIdInteger.get());
375 rpc.append("\" ");
376 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
377 rpc.append(GET_OPEN).append(NEW_LINE);
378 if (filterSchema != null) {
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900379 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900380 rpc.append(filterSchema).append(NEW_LINE);
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900381 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Akihiro Yamanouchi5e5d4df2016-06-08 17:06:33 +0900382 }
383 if (withDefaultsMode != null) {
384 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
385 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
386 }
387 rpc.append(GET_CLOSE).append(NEW_LINE);
388 rpc.append(RPC_CLOSE).append(NEW_LINE);
389 rpc.append(ENDPATTERN);
390 String reply = sendRequest(rpc.toString());
391 checkReply(reply);
392 return reply;
393 }
394
395 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300396 public String getConfig(TargetConfig netconfTargetConfig) throws NetconfException {
397 return getConfig(netconfTargetConfig, null);
andreaeb70a942015-10-16 21:34:46 -0700398 }
399
400 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300401 public String getConfig(String netconfTargetConfig) throws NetconfException {
402 return getConfig(TargetConfig.valueOf(netconfTargetConfig));
403 }
404
405 @Override
406 public String getConfig(String netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
407 return getConfig(TargetConfig.valueOf(netconfTargetConfig), configurationFilterSchema);
408 }
409
410 @Override
411 public String getConfig(TargetConfig netconfTargetConfig, String configurationSchema) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800412 StringBuilder rpc = new StringBuilder(XML_HEADER);
413 rpc.append("<rpc ");
414 rpc.append(MESSAGE_ID_STRING);
415 rpc.append(EQUAL);
416 rpc.append("\"");
417 rpc.append(messageIdInteger.get());
418 rpc.append("\" ");
419 rpc.append("xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700420 rpc.append("<get-config>\n");
421 rpc.append("<source>\n");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300422 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700423 rpc.append("</source>");
424 if (configurationSchema != null) {
425 rpc.append("<filter type=\"subtree\">\n");
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800426 rpc.append(configurationSchema).append("\n");
andreaeb70a942015-10-16 21:34:46 -0700427 rpc.append("</filter>\n");
428 }
429 rpc.append("</get-config>\n");
430 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800431 rpc.append(ENDPATTERN);
432 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800433 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700434 }
435
436 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800437 public boolean editConfig(String newConfiguration) throws NetconfException {
438 newConfiguration = newConfiguration + ENDPATTERN;
439 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700440 }
441
442 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300443 public boolean editConfig(String netconfTargetConfig, String mode, String newConfiguration)
444 throws NetconfException {
445 return editConfig(TargetConfig.valueOf(netconfTargetConfig), mode, newConfiguration);
446 }
447
448 @Override
449 public boolean editConfig(TargetConfig netconfTargetConfig, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800450 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800451 newConfiguration = newConfiguration.trim();
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800452 StringBuilder rpc = new StringBuilder(XML_HEADER);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900453 rpc.append(RPC_OPEN);
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800454 rpc.append(MESSAGE_ID_STRING);
455 rpc.append(EQUAL);
456 rpc.append("\"");
457 rpc.append(messageIdInteger.get());
458 rpc.append("\" ");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900459 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
460 rpc.append(EDIT_CONFIG_OPEN).append("\n");
461 rpc.append(TARGET_OPEN);
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300462 rpc.append("<").append(netconfTargetConfig).append("/>");
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900463 rpc.append(TARGET_CLOSE).append("\n");
464 if (mode != null) {
465 rpc.append(DEFAULT_OPERATION_OPEN);
466 rpc.append(mode);
467 rpc.append(DEFAULT_OPERATION_CLOSE).append("\n");
468 }
469 rpc.append(CONFIG_OPEN).append("\n");
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800470 rpc.append(newConfiguration);
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900471 rpc.append(CONFIG_CLOSE).append("\n");
472 rpc.append(EDIT_CONFIG_CLOSE).append("\n");
473 rpc.append(RPC_CLOSE);
Andrea Campanella101417d2015-12-11 17:58:07 -0800474 rpc.append(ENDPATTERN);
Konstantinos Kanonakis1b8b5592016-09-09 14:34:37 -0500475 log.debug(rpc.toString());
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900476 String reply = sendRequest(rpc.toString());
477 return checkReply(reply);
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800478 }
479
480 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300481 public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
482 return copyConfig(TargetConfig.valueOf(netconfTargetConfig), newConfiguration);
483 }
484
485 @Override
486 public boolean copyConfig(TargetConfig netconfTargetConfig, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800487 throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700488 newConfiguration = newConfiguration.trim();
yoonseona1957552016-11-02 15:53:07 -0400489 if (!newConfiguration.startsWith("<config>")) {
490 newConfiguration = "<config>" + newConfiguration
491 + "</config>";
andreaeb70a942015-10-16 21:34:46 -0700492 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800493 StringBuilder rpc = new StringBuilder(XML_HEADER);
yoonseon55faf852016-11-02 14:59:12 -0400494 rpc.append(RPC_OPEN);
495 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
andreaeb70a942015-10-16 21:34:46 -0700496 rpc.append("<copy-config>");
497 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300498 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700499 rpc.append("</target>");
500 rpc.append("<source>");
yoonseon25e54fc2016-11-02 13:07:43 -0400501 rpc.append(newConfiguration);
andreaeb70a942015-10-16 21:34:46 -0700502 rpc.append("</source>");
503 rpc.append("</copy-config>");
504 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800505 rpc.append(ENDPATTERN);
506 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700507 }
508
509 @Override
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300510 public boolean deleteConfig(String netconfTargetConfig) throws NetconfException {
511 return deleteConfig(TargetConfig.valueOf(netconfTargetConfig));
512 }
513
514 @Override
515 public boolean deleteConfig(TargetConfig netconfTargetConfig) throws NetconfException {
516 if (netconfTargetConfig.equals("running")) {
andreaeb70a942015-10-16 21:34:46 -0700517 log.warn("Target configuration for delete operation can't be \"running\"",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300518 netconfTargetConfig);
andreaeb70a942015-10-16 21:34:46 -0700519 return false;
520 }
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800521 StringBuilder rpc = new StringBuilder(XML_HEADER);
andreaeb70a942015-10-16 21:34:46 -0700522 rpc.append("<rpc>");
523 rpc.append("<delete-config>");
524 rpc.append("<target>");
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300525 rpc.append("<").append(netconfTargetConfig).append("/>");
andreaeb70a942015-10-16 21:34:46 -0700526 rpc.append("</target>");
527 rpc.append("</delete-config>");
528 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800529 rpc.append(ENDPATTERN);
530 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700531 }
532
533 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700534 public boolean lock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800535 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700536 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700537 rpc.append("<lock>");
538 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700539 rpc.append("<");
540 rpc.append(configType);
541 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700542 rpc.append("</target>");
543 rpc.append("</lock>");
544 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800545 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700546 String lockReply = sendRequest(rpc.toString());
547 return checkReply(lockReply);
andreaeb70a942015-10-16 21:34:46 -0700548 }
549
550 @Override
helenyrwu0407c642016-06-09 12:01:30 -0700551 public boolean unlock(String configType) throws NetconfException {
Andrea Campanellab029b9e2016-01-29 11:05:36 -0800552 StringBuilder rpc = new StringBuilder(XML_HEADER);
helenyrwu0407c642016-06-09 12:01:30 -0700553 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
andreaeb70a942015-10-16 21:34:46 -0700554 rpc.append("<unlock>");
555 rpc.append("<target>");
helenyrwu0407c642016-06-09 12:01:30 -0700556 rpc.append("<");
557 rpc.append(configType);
558 rpc.append("/>");
andreaeb70a942015-10-16 21:34:46 -0700559 rpc.append("</target>");
560 rpc.append("</unlock>");
561 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800562 rpc.append(ENDPATTERN);
helenyrwu0407c642016-06-09 12:01:30 -0700563 String unlockReply = sendRequest(rpc.toString());
564 return checkReply(unlockReply);
565 }
566
567 @Override
568 public boolean lock() throws NetconfException {
569 return lock("running");
570 }
571
572 @Override
573 public boolean unlock() throws NetconfException {
574 return unlock("running");
andreaeb70a942015-10-16 21:34:46 -0700575 }
576
577 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800578 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700579 return close(false);
580 }
581
Andrea Campanella101417d2015-12-11 17:58:07 -0800582 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700583 StringBuilder rpc = new StringBuilder();
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700584 rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
andreaeb70a942015-10-16 21:34:46 -0700585 if (force) {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700586 rpc.append("<kill-session/>");
andreaeb70a942015-10-16 21:34:46 -0700587 } else {
Andrea Campanella7e6200a2016-03-21 09:48:40 -0700588 rpc.append("<close-session/>");
andreaeb70a942015-10-16 21:34:46 -0700589 }
andreaeb70a942015-10-16 21:34:46 -0700590 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800591 rpc.append(ENDPATTERN);
592 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700593 }
594
595 @Override
596 public String getSessionId() {
597 if (serverCapabilities.contains("<session-id>")) {
598 String[] outer = serverCapabilities.split("<session-id>");
599 Preconditions.checkArgument(outer.length != 1,
600 "Error in retrieving the session id");
601 String[] value = outer[1].split("</session-id>");
602 Preconditions.checkArgument(value.length != 1,
603 "Error in retrieving the session id");
604 return value[0];
605 } else {
606 return String.valueOf(-1);
607 }
608 }
609
610 @Override
611 public String getServerCapabilities() {
612 return serverCapabilities;
613 }
614
615 @Override
616 public void setDeviceCapabilities(List<String> capabilities) {
617 deviceCapabilities = capabilities;
618 }
619
Andrea Campanella101417d2015-12-11 17:58:07 -0800620 @Override
621 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700622 streamHandler.addDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800623 }
624
625 @Override
626 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
helenyrwu0407c642016-06-09 12:01:30 -0700627 streamHandler.removeDeviceEventListener(listener);
Andrea Campanella101417d2015-12-11 17:58:07 -0800628 }
629
630 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700631 if (reply != null) {
632 if (!reply.contains("<rpc-error>")) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900633 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700634 return true;
635 } else if (reply.contains("<ok/>")
636 || (reply.contains("<rpc-error>")
637 && reply.contains("warning"))) {
Akihiro Yamanouchid4912842016-07-01 10:38:46 +0900638 log.debug("Device {} sent reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700639 return true;
640 }
641 }
Andrea Campanellad264b492016-03-01 09:46:06 -0800642 log.warn("Device {} has error in reply {}", deviceInfo, reply);
andreaeb70a942015-10-16 21:34:46 -0700643 return false;
644 }
645
Andrea Campanella101417d2015-12-11 17:58:07 -0800646 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700647
Andrea Campanella101417d2015-12-11 17:58:07 -0800648 @Override
Andrea Campanellac3627842017-04-04 18:06:54 +0200649 public void notify(NetconfDeviceOutputEvent event) {
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200650 Optional<Integer> messageId = event.getMessageID();
Andrea Campanella0cee0b62017-04-03 20:02:54 +0200651 log.debug("messageID {}, waiting replies messageIDs {}", messageId,
652 replies.keySet());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200653 if (!messageId.isPresent()) {
654 errorReplies.add(event.getMessagePayload());
Andrea Campanellad264b492016-03-01 09:46:06 -0800655 log.error("Device {} sent error reply {}",
656 event.getDeviceInfo(), event.getMessagePayload());
Andreas Papazoisd4712e22016-02-10 15:59:55 +0200657 return;
658 }
659 CompletableFuture<String> completedReply =
660 replies.get(messageId.get());
Akihiro Yamanouchi45122222016-07-15 13:13:11 +0900661 if (completedReply != null) {
662 completedReply.complete(event.getMessagePayload());
663 }
andreaeb70a942015-10-16 21:34:46 -0700664 }
665 }
andreaeb70a942015-10-16 21:34:46 -0700666}