blob: 7f544dce2a2a2e8db650969a362e37ae747ba1fb [file] [log] [blame]
andreaeb70a942015-10-16 21:34:46 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
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
19import ch.ethz.ssh2.Connection;
20import ch.ethz.ssh2.Session;
21import com.google.common.base.Preconditions;
22import org.onosproject.netconf.NetconfDeviceInfo;
Andrea Campanella101417d2015-12-11 17:58:07 -080023import org.onosproject.netconf.NetconfDeviceOutputEvent;
24import org.onosproject.netconf.NetconfDeviceOutputEventListener;
25import org.onosproject.netconf.NetconfException;
andreaeb70a942015-10-16 21:34:46 -070026import org.onosproject.netconf.NetconfSession;
27import org.slf4j.Logger;
28import org.slf4j.LoggerFactory;
29
andreaeb70a942015-10-16 21:34:46 -070030import java.io.IOException;
andreaeb70a942015-10-16 21:34:46 -070031import java.io.PrintWriter;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080032import java.util.Collections;
Andrea Campanella101417d2015-12-11 17:58:07 -080033import java.util.HashMap;
andreaeb70a942015-10-16 21:34:46 -070034import java.util.List;
Andrea Campanella101417d2015-12-11 17:58:07 -080035import java.util.Map;
36import java.util.concurrent.CompletableFuture;
37import java.util.concurrent.atomic.AtomicInteger;
38
andreaeb70a942015-10-16 21:34:46 -070039
40/**
41 * Implementation of a NETCONF session to talk to a device.
42 */
43public class NetconfSessionImpl implements NetconfSession {
44
Andrea Campanella101417d2015-12-11 17:58:07 -080045 private static final Logger log = LoggerFactory
andreaeb70a942015-10-16 21:34:46 -070046 .getLogger(NetconfSessionImpl.class);
Andrea Campanella101417d2015-12-11 17:58:07 -080047
48
andreaeb70a942015-10-16 21:34:46 -070049 private static final int CONNECTION_TIMEOUT = 0;
Andrea Campanella101417d2015-12-11 17:58:07 -080050 private static final String ENDPATTERN = "]]>]]>";
51 private static final AtomicInteger MESSAGE_ID_INTEGER = new AtomicInteger(0);
52 private static final String MESSAGE_ID_STRING = "message-id";
53 private static final String HELLO = "hello";
54 private static final String NEW_LINE = "\n";
andreaeb70a942015-10-16 21:34:46 -070055
56
57 private Connection netconfConnection;
58 private NetconfDeviceInfo deviceInfo;
59 private Session sshSession;
60 private boolean connectionActive;
andreaeb70a942015-10-16 21:34:46 -070061 private PrintWriter out = null;
andreaeb70a942015-10-16 21:34:46 -070062 private List<String> deviceCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -080063 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
andreaeb70a942015-10-16 21:34:46 -070064 private String serverCapabilities;
Andrea Campanella101417d2015-12-11 17:58:07 -080065 private NetconfStreamHandler t;
66 private Map<Integer, CompletableFuture<String>> replies;
andreaeb70a942015-10-16 21:34:46 -070067
68
Andrea Campanella101417d2015-12-11 17:58:07 -080069 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -070070 this.deviceInfo = deviceInfo;
71 connectionActive = false;
Andrea Campanella101417d2015-12-11 17:58:07 -080072 replies = new HashMap<>();
andreaeb70a942015-10-16 21:34:46 -070073 startConnection();
74 }
75
76
Andrea Campanella101417d2015-12-11 17:58:07 -080077 private void startConnection() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -070078 if (!connectionActive) {
79 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
Andrea Campanella101417d2015-12-11 17:58:07 -080080 try {
81 netconfConnection.connect(null, CONNECTION_TIMEOUT, 5000);
82 } catch (IOException e) {
83 throw new NetconfException("Cannot open a connection with device" + deviceInfo, e);
84 }
andreaeb70a942015-10-16 21:34:46 -070085 boolean isAuthenticated;
86 try {
87 if (deviceInfo.getKeyFile() != null) {
88 isAuthenticated = netconfConnection.authenticateWithPublicKey(
89 deviceInfo.name(), deviceInfo.getKeyFile(),
90 deviceInfo.password());
91 } else {
Andrea Campanella101417d2015-12-11 17:58:07 -080092 log.debug("Authenticating to device {} with username {}",
93 deviceInfo.getDeviceId(), deviceInfo.name(), deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -070094 isAuthenticated = netconfConnection.authenticateWithPassword(
95 deviceInfo.name(), deviceInfo.password());
96 }
97 } catch (IOException e) {
Andrea Campanella101417d2015-12-11 17:58:07 -080098 log.error("Authentication connection to device " +
99 deviceInfo.getDeviceId() + " failed:" +
100 e.getMessage());
101 throw new NetconfException("Authentication connection to device " +
102 deviceInfo.getDeviceId() + " failed", e);
andreaeb70a942015-10-16 21:34:46 -0700103 }
104
105 connectionActive = true;
106 Preconditions.checkArgument(isAuthenticated,
Andrea Campanella101417d2015-12-11 17:58:07 -0800107 "Authentication to device {} with username " +
108 "{} Failed",
109 deviceInfo.getDeviceId(), deviceInfo.name(),
110 deviceInfo.password());
andreaeb70a942015-10-16 21:34:46 -0700111 startSshSession();
112 }
113 }
114
Andrea Campanella101417d2015-12-11 17:58:07 -0800115 private void startSshSession() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700116 try {
117 sshSession = netconfConnection.openSession();
118 sshSession.startSubSystem("netconf");
andreaeb70a942015-10-16 21:34:46 -0700119 out = new PrintWriter(sshSession.getStdin());
Andrea Campanella101417d2015-12-11 17:58:07 -0800120 t = new NetconfStreamThread(sshSession.getStdout(), sshSession.getStdin(),
121 sshSession.getStderr(), deviceInfo,
122 new NetconfSessionDelegateImpl());
123 this.addDeviceOutputListener(new NetconfDeviceOutputEventListenerImpl(deviceInfo));
andreaeb70a942015-10-16 21:34:46 -0700124 sendHello();
125 } catch (IOException e) {
Andrea Campanella101417d2015-12-11 17:58:07 -0800126 log.error("Failed to create ch.ethz.ssh2.Session session:" +
127 e.getMessage());
128 throw new NetconfException("Failed to create ch.ethz.ssh2.Session session with device" +
129 deviceInfo, e);
andreaeb70a942015-10-16 21:34:46 -0700130 }
131 }
132
133 private void sendHello() throws IOException {
Andrea Campanella101417d2015-12-11 17:58:07 -0800134 serverCapabilities = sendRequest(createHelloString());
andreaeb70a942015-10-16 21:34:46 -0700135 }
136
137 private String createHelloString() {
138 StringBuilder hellobuffer = new StringBuilder();
139 hellobuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
140 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
141 hellobuffer.append(" <capabilities>\n");
142 deviceCapabilities.forEach(
143 cap -> hellobuffer.append(" <capability>" + cap + "</capability>\n"));
144 hellobuffer.append(" </capabilities>\n");
145 hellobuffer.append("</hello>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800146 hellobuffer.append(ENDPATTERN);
andreaeb70a942015-10-16 21:34:46 -0700147 return hellobuffer.toString();
148
149 }
150
Andrea Campanella101417d2015-12-11 17:58:07 -0800151 private void checkAndRestablishSession() throws NetconfException {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800152 if (sshSession.getState() != 2) {
153 try {
154 startSshSession();
155 } catch (IOException e) {
Andrea Campanella101417d2015-12-11 17:58:07 -0800156 log.debug("The connection with {} had to be reopened", deviceInfo.getDeviceId());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800157 try {
158 startConnection();
159 } catch (IOException e2) {
160 log.error("No connection {} for device, exception {}", netconfConnection, e2);
Andrea Campanella101417d2015-12-11 17:58:07 -0800161 throw new NetconfException("Cannot re-open the connection with device" + deviceInfo, e);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800162 }
163 }
164 }
165 }
166
andreaeb70a942015-10-16 21:34:46 -0700167 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800168 public String requestSync(String request) throws NetconfException {
169 String reply = sendRequest(request + NEW_LINE + ENDPATTERN);
170 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700171 }
172
173 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800174 public CompletableFuture<String> request(String request) {
175 CompletableFuture<String> ftrep = t.sendMessage(request);
176 replies.put(MESSAGE_ID_INTEGER.get(), ftrep);
177 return ftrep;
178 }
179
180 private String sendRequest(String request) throws NetconfException {
181 checkAndRestablishSession();
182 //FIXME find out a better way to enforce the presence of message-id
183 if (!request.contains(MESSAGE_ID_STRING) && !request.contains(HELLO)) {
184 request = request.replaceFirst("\">", "\" message-id=\""
185 + MESSAGE_ID_INTEGER.get() + "\"" + ">");
186 }
187 CompletableFuture<String> futureReply = request(request);
188 MESSAGE_ID_INTEGER.incrementAndGet();
189 String rp = futureReply.join();
190 log.debug("Reply from device {}", rp);
191 return rp;
192 }
193
194 @Override
195 public String get(String request) throws NetconfException {
196 return requestSync(request);
197 }
198
199 @Override
200 public String getConfig(String targetConfiguration) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700201 return getConfig(targetConfiguration, null);
202 }
203
204 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800205 public String getConfig(String targetConfiguration, String configurationSchema) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700206 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800207 rpc.append("<rpc message-id=\"" + MESSAGE_ID_INTEGER.get() + "\" "
andreaeb70a942015-10-16 21:34:46 -0700208 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
209 rpc.append("<get-config>\n");
210 rpc.append("<source>\n");
211 rpc.append("<" + targetConfiguration + "/>");
212 rpc.append("</source>");
213 if (configurationSchema != null) {
214 rpc.append("<filter type=\"subtree\">\n");
215 rpc.append(configurationSchema + "\n");
216 rpc.append("</filter>\n");
217 }
218 rpc.append("</get-config>\n");
219 rpc.append("</rpc>\n");
Andrea Campanella101417d2015-12-11 17:58:07 -0800220 rpc.append(ENDPATTERN);
221 String reply = sendRequest(rpc.toString());
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800222 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700223 }
224
225 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800226 public boolean editConfig(String newConfiguration) throws NetconfException {
227 newConfiguration = newConfiguration + ENDPATTERN;
228 return checkReply(sendRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700229 }
230
231 @Override
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800232 public boolean editConfig(String targetConfiguration, String mode, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800233 throws NetconfException {
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800234 newConfiguration = newConfiguration.trim();
235 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800236 rpc.append("<rpc message-id=\"" + MESSAGE_ID_INTEGER.get() + "\" "
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800237 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
238 rpc.append("<edit-config>");
239 rpc.append("<target>");
240 rpc.append("<" + targetConfiguration + "/>");
241 rpc.append("</target>");
242 rpc.append("<default-operation>");
243 rpc.append(mode);
244 rpc.append("</default-operation>");
245 rpc.append("<config>");
246 rpc.append(newConfiguration);
247 rpc.append("</config>");
248 rpc.append("</edit-config>");
249 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800250 rpc.append(ENDPATTERN);
251 return checkReply(sendRequest(rpc.toString()));
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800252 }
253
254 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800255 public boolean copyConfig(String targetConfiguration, String newConfiguration)
Andrea Campanella101417d2015-12-11 17:58:07 -0800256 throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700257 newConfiguration = newConfiguration.trim();
258 if (!newConfiguration.startsWith("<configuration>")) {
259 newConfiguration = "<configuration>" + newConfiguration
260 + "</configuration>";
261 }
262 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
263 "encoding=\"UTF-8\"?>");
264 rpc.append("<rpc>");
265 rpc.append("<copy-config>");
266 rpc.append("<target>");
267 rpc.append("<" + targetConfiguration + "/>");
268 rpc.append("</target>");
269 rpc.append("<source>");
270 rpc.append("<" + newConfiguration + "/>");
271 rpc.append("</source>");
272 rpc.append("</copy-config>");
273 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800274 rpc.append(ENDPATTERN);
275 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700276 }
277
278 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800279 public boolean deleteConfig(String targetConfiguration) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700280 if (targetConfiguration.equals("running")) {
281 log.warn("Target configuration for delete operation can't be \"running\"",
282 targetConfiguration);
283 return false;
284 }
285 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
286 "encoding=\"UTF-8\"?>");
287 rpc.append("<rpc>");
288 rpc.append("<delete-config>");
289 rpc.append("<target>");
290 rpc.append("<" + targetConfiguration + "/>");
291 rpc.append("</target>");
292 rpc.append("</delete-config>");
293 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800294 rpc.append(ENDPATTERN);
295 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700296 }
297
298 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800299 public boolean lock() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700300 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
301 "encoding=\"UTF-8\"?>");
302 rpc.append("<rpc>");
303 rpc.append("<lock>");
304 rpc.append("<target>");
305 rpc.append("<candidate/>");
306 rpc.append("</target>");
307 rpc.append("</lock>");
308 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800309 rpc.append(ENDPATTERN);
310 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700311 }
312
313 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800314 public boolean unlock() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700315 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
316 "encoding=\"UTF-8\"?>");
317 rpc.append("<rpc>");
318 rpc.append("<unlock>");
319 rpc.append("<target>");
320 rpc.append("<candidate/>");
321 rpc.append("</target>");
322 rpc.append("</unlock>");
323 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800324 rpc.append(ENDPATTERN);
325 return checkReply(sendRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700326 }
327
328 @Override
Andrea Campanella101417d2015-12-11 17:58:07 -0800329 public boolean close() throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700330 return close(false);
331 }
332
Andrea Campanella101417d2015-12-11 17:58:07 -0800333 private boolean close(boolean force) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700334 StringBuilder rpc = new StringBuilder();
335 rpc.append("<rpc>");
336 if (force) {
337 rpc.append("<kill-configuration/>");
338 } else {
339 rpc.append("<close-configuration/>");
340 }
341 rpc.append("<close-configuration/>");
342 rpc.append("</rpc>");
Andrea Campanella101417d2015-12-11 17:58:07 -0800343 rpc.append(ENDPATTERN);
344 return checkReply(sendRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700345 }
346
347 @Override
348 public String getSessionId() {
349 if (serverCapabilities.contains("<session-id>")) {
350 String[] outer = serverCapabilities.split("<session-id>");
351 Preconditions.checkArgument(outer.length != 1,
352 "Error in retrieving the session id");
353 String[] value = outer[1].split("</session-id>");
354 Preconditions.checkArgument(value.length != 1,
355 "Error in retrieving the session id");
356 return value[0];
357 } else {
358 return String.valueOf(-1);
359 }
360 }
361
362 @Override
363 public String getServerCapabilities() {
364 return serverCapabilities;
365 }
366
367 @Override
368 public void setDeviceCapabilities(List<String> capabilities) {
369 deviceCapabilities = capabilities;
370 }
371
Andrea Campanella101417d2015-12-11 17:58:07 -0800372 @Override
373 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
374 t.addDeviceEventListener(listener);
375 }
376
377 @Override
378 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
379 t.removeDeviceEventListener(listener);
380 }
381
382 private boolean checkReply(String reply) throws NetconfException {
andreaeb70a942015-10-16 21:34:46 -0700383 if (reply != null) {
384 if (!reply.contains("<rpc-error>")) {
385 return true;
386 } else if (reply.contains("<ok/>")
387 || (reply.contains("<rpc-error>")
388 && reply.contains("warning"))) {
389 return true;
390 }
391 }
Andrea Campanella101417d2015-12-11 17:58:07 -0800392 log.warn("Device " + deviceInfo + "has error in reply {}", reply);
andreaeb70a942015-10-16 21:34:46 -0700393 return false;
394 }
395
Andrea Campanella101417d2015-12-11 17:58:07 -0800396 public class NetconfSessionDelegateImpl implements NetconfSessionDelegate {
andreaeb70a942015-10-16 21:34:46 -0700397
Andrea Campanella101417d2015-12-11 17:58:07 -0800398 @Override
399 public void notify(NetconfDeviceOutputEvent event) {
400 CompletableFuture<String> completedReply = replies.get(event.getMessageID());
401 completedReply.complete(event.getMessagePayload());
andreaeb70a942015-10-16 21:34:46 -0700402 }
403 }
404
Andrea Campanella101417d2015-12-11 17:58:07 -0800405
andreaeb70a942015-10-16 21:34:46 -0700406}