blob: ab7c746b2b26c4238d4c72ac040464ebea0ee8f1 [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;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080021import ch.ethz.ssh2.StreamGobbler;
andreaeb70a942015-10-16 21:34:46 -070022import com.google.common.base.Preconditions;
23import org.onosproject.netconf.NetconfDeviceInfo;
24import org.onosproject.netconf.NetconfSession;
25import org.slf4j.Logger;
26import org.slf4j.LoggerFactory;
27
28import java.io.BufferedReader;
29import java.io.IOException;
30import java.io.InputStreamReader;
31import java.io.PrintWriter;
32import java.io.StringWriter;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080033import java.util.Collections;
andreaeb70a942015-10-16 21:34:46 -070034import java.util.List;
35
36/**
37 * Implementation of a NETCONF session to talk to a device.
38 */
39public class NetconfSessionImpl implements NetconfSession {
40
41 public static final Logger log = LoggerFactory
42 .getLogger(NetconfSessionImpl.class);
43 private static final int CONNECTION_TIMEOUT = 0;
44
45
46 private Connection netconfConnection;
47 private NetconfDeviceInfo deviceInfo;
48 private Session sshSession;
49 private boolean connectionActive;
50 private BufferedReader bufferReader = null;
51 private PrintWriter out = null;
52 private int messageID = 0;
Andrea Campanella1cd641b2015-12-07 17:28:34 -080053 //TODO inject these capabilites from yang model provided by app
andreaeb70a942015-10-16 21:34:46 -070054 private List<String> deviceCapabilities =
Andrea Campanella1cd641b2015-12-07 17:28:34 -080055 Collections.singletonList("urn:ietf:params:netconf:base:1.0");
andreaeb70a942015-10-16 21:34:46 -070056 private String serverCapabilities;
57 private String endpattern = "]]>]]>";
58
59
60 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws IOException {
61 this.deviceInfo = deviceInfo;
62 connectionActive = false;
63 startConnection();
64 }
65
66
67 private void startConnection() throws IOException {
68 if (!connectionActive) {
69 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
70 netconfConnection.connect(null, CONNECTION_TIMEOUT, 0);
71 boolean isAuthenticated;
72 try {
73 if (deviceInfo.getKeyFile() != null) {
74 isAuthenticated = netconfConnection.authenticateWithPublicKey(
75 deviceInfo.name(), deviceInfo.getKeyFile(),
76 deviceInfo.password());
77 } else {
78 log.info("authenticate with username {} and password {}",
79 deviceInfo.name(), deviceInfo.password());
80 isAuthenticated = netconfConnection.authenticateWithPassword(
81 deviceInfo.name(), deviceInfo.password());
82 }
83 } catch (IOException e) {
84 throw new IOException("Authentication connection failed:" +
85 e.getMessage());
86 }
87
88 connectionActive = true;
89 Preconditions.checkArgument(isAuthenticated,
90 "Authentication password and username failed");
91 startSshSession();
92 }
93 }
94
95 private void startSshSession() throws IOException {
96 try {
97 sshSession = netconfConnection.openSession();
98 sshSession.startSubSystem("netconf");
Andrea Campanella1cd641b2015-12-07 17:28:34 -080099 bufferReader = new BufferedReader(new InputStreamReader(new StreamGobbler(
100 sshSession.getStdout())));
andreaeb70a942015-10-16 21:34:46 -0700101 out = new PrintWriter(sshSession.getStdin());
102 sendHello();
103 } catch (IOException e) {
104 throw new IOException("Failed to create ch.ethz.ssh2.Session session:" +
105 e.getMessage());
106 }
107 }
108
109 private void sendHello() throws IOException {
110 serverCapabilities = doRequest(createHelloString());
111 }
112
113 private String createHelloString() {
114 StringBuilder hellobuffer = new StringBuilder();
115 hellobuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
116 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
117 hellobuffer.append(" <capabilities>\n");
118 deviceCapabilities.forEach(
119 cap -> hellobuffer.append(" <capability>" + cap + "</capability>\n"));
120 hellobuffer.append(" </capabilities>\n");
121 hellobuffer.append("</hello>\n");
122 hellobuffer.append(endpattern);
123 return hellobuffer.toString();
124
125 }
126
127 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800128 public String doRPC(String request) throws IOException {
129 String reply = doRequest(request);
130 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700131 }
132
133 private String doRequest(String request) throws IOException {
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800134 //log.info("sshState " + sshSession.getState() + "request" + request);
135 checkAndRestablishSession();
136 //log.info("sshState after" + sshSession.getState());
andreaeb70a942015-10-16 21:34:46 -0700137 out.print(request);
138 out.flush();
139 messageID++;
140 return readOne();
141 }
142
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800143 private void checkAndRestablishSession() throws IOException {
144 if (sshSession.getState() != 2) {
145 try {
146 startSshSession();
147 } catch (IOException e) {
148 log.info("the connection had to be reopened");
149 try {
150 startConnection();
151 } catch (IOException e2) {
152 log.error("No connection {} for device, exception {}", netconfConnection, e2);
153 throw new IOException(e.getMessage());
154 //TODO remove device from ONOS
155 }
156 }
157 }
158 }
159
andreaeb70a942015-10-16 21:34:46 -0700160 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800161 public String get(String request) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700162 return doRPC(request);
163 }
164
165 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800166 public String getConfig(String targetConfiguration) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700167 return getConfig(targetConfiguration, null);
168 }
169
170 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800171 public String getConfig(String targetConfiguration, String configurationSchema) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700172 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
173 rpc.append("<rpc message-id=\"" + messageID + "\" "
174 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
175 rpc.append("<get-config>\n");
176 rpc.append("<source>\n");
177 rpc.append("<" + targetConfiguration + "/>");
178 rpc.append("</source>");
179 if (configurationSchema != null) {
180 rpc.append("<filter type=\"subtree\">\n");
181 rpc.append(configurationSchema + "\n");
182 rpc.append("</filter>\n");
183 }
184 rpc.append("</get-config>\n");
185 rpc.append("</rpc>\n");
186 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800187 String reply = doRequest(rpc.toString());
188 return checkReply(reply) ? reply : "ERROR " + reply;
andreaeb70a942015-10-16 21:34:46 -0700189 }
190
191 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800192 public boolean editConfig(String newConfiguration) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700193 newConfiguration = newConfiguration + endpattern;
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800194 return checkReply(doRequest(newConfiguration));
andreaeb70a942015-10-16 21:34:46 -0700195 }
196
197 @Override
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800198 public boolean editConfig(String targetConfiguration, String mode, String newConfiguration)
199 throws IOException {
200 newConfiguration = newConfiguration.trim();
201 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
202 rpc.append("<rpc message-id=\"" + messageID + "\" "
203 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
204 rpc.append("<edit-config>");
205 rpc.append("<target>");
206 rpc.append("<" + targetConfiguration + "/>");
207 rpc.append("</target>");
208 rpc.append("<default-operation>");
209 rpc.append(mode);
210 rpc.append("</default-operation>");
211 rpc.append("<config>");
212 rpc.append(newConfiguration);
213 rpc.append("</config>");
214 rpc.append("</edit-config>");
215 rpc.append("</rpc>");
216 rpc.append(endpattern);
217 return checkReply(doRequest(rpc.toString()));
218 }
219
220 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800221 public boolean copyConfig(String targetConfiguration, String newConfiguration)
222 throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700223 newConfiguration = newConfiguration.trim();
224 if (!newConfiguration.startsWith("<configuration>")) {
225 newConfiguration = "<configuration>" + newConfiguration
226 + "</configuration>";
227 }
228 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
229 "encoding=\"UTF-8\"?>");
230 rpc.append("<rpc>");
231 rpc.append("<copy-config>");
232 rpc.append("<target>");
233 rpc.append("<" + targetConfiguration + "/>");
234 rpc.append("</target>");
235 rpc.append("<source>");
236 rpc.append("<" + newConfiguration + "/>");
237 rpc.append("</source>");
238 rpc.append("</copy-config>");
239 rpc.append("</rpc>");
240 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800241 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700242 }
243
244 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800245 public boolean deleteConfig(String targetConfiguration) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700246 if (targetConfiguration.equals("running")) {
247 log.warn("Target configuration for delete operation can't be \"running\"",
248 targetConfiguration);
249 return false;
250 }
251 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
252 "encoding=\"UTF-8\"?>");
253 rpc.append("<rpc>");
254 rpc.append("<delete-config>");
255 rpc.append("<target>");
256 rpc.append("<" + targetConfiguration + "/>");
257 rpc.append("</target>");
258 rpc.append("</delete-config>");
259 rpc.append("</rpc>");
260 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800261 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700262 }
263
264 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800265 public boolean lock() throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700266 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
267 "encoding=\"UTF-8\"?>");
268 rpc.append("<rpc>");
269 rpc.append("<lock>");
270 rpc.append("<target>");
271 rpc.append("<candidate/>");
272 rpc.append("</target>");
273 rpc.append("</lock>");
274 rpc.append("</rpc>");
275 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800276 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700277 }
278
279 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800280 public boolean unlock() throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700281 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
282 "encoding=\"UTF-8\"?>");
283 rpc.append("<rpc>");
284 rpc.append("<unlock>");
285 rpc.append("<target>");
286 rpc.append("<candidate/>");
287 rpc.append("</target>");
288 rpc.append("</unlock>");
289 rpc.append("</rpc>");
290 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800291 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700292 }
293
294 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800295 public boolean close() throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700296 return close(false);
297 }
298
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800299 private boolean close(boolean force) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700300 StringBuilder rpc = new StringBuilder();
301 rpc.append("<rpc>");
302 if (force) {
303 rpc.append("<kill-configuration/>");
304 } else {
305 rpc.append("<close-configuration/>");
306 }
307 rpc.append("<close-configuration/>");
308 rpc.append("</rpc>");
309 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800310 return checkReply(doRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700311 }
312
313 @Override
314 public String getSessionId() {
315 if (serverCapabilities.contains("<session-id>")) {
316 String[] outer = serverCapabilities.split("<session-id>");
317 Preconditions.checkArgument(outer.length != 1,
318 "Error in retrieving the session id");
319 String[] value = outer[1].split("</session-id>");
320 Preconditions.checkArgument(value.length != 1,
321 "Error in retrieving the session id");
322 return value[0];
323 } else {
324 return String.valueOf(-1);
325 }
326 }
327
328 @Override
329 public String getServerCapabilities() {
330 return serverCapabilities;
331 }
332
333 @Override
334 public void setDeviceCapabilities(List<String> capabilities) {
335 deviceCapabilities = capabilities;
336 }
337
338 private boolean checkReply(String reply) {
339 if (reply != null) {
340 if (!reply.contains("<rpc-error>")) {
341 return true;
342 } else if (reply.contains("<ok/>")
343 || (reply.contains("<rpc-error>")
344 && reply.contains("warning"))) {
345 return true;
346 }
347 }
Andrea Campanellaf4fd0352015-12-14 17:03:05 -0800348 log.warn("Error in reply {}", reply);
andreaeb70a942015-10-16 21:34:46 -0700349 return false;
350 }
351
352 private String readOne() throws IOException {
353 //TODO try a simple string
354 final StringWriter reply = new StringWriter();
355 while (true) {
356 int charRead = bufferReader.read();
357 if (charRead == -1) {
358 throw new IOException("Session closed");
359 }
360
361 for (int i = 0; i < endpattern.length(); i++) {
362 if (charRead == endpattern.charAt(i)) {
363 if (i < endpattern.length() - 1) {
364 charRead = bufferReader.read();
365 } else {
366 return reply.getBuffer().toString();
367 }
368 } else {
369 String s = endpattern.substring(0, i);
370 for (int j = 0; i < s.length(); j++) {
371 reply.write(s.charAt(j));
372 }
373 reply.write(charRead);
374 break;
375 }
376 }
377 }
378 }
379
380}