blob: 1eff32a234bcb26b01125cbd780a3938a7cf19f0 [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 Campanella1cd641b2015-12-07 17:28:34 -0800198 public boolean copyConfig(String targetConfiguration, String newConfiguration)
199 throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700200 newConfiguration = newConfiguration.trim();
201 if (!newConfiguration.startsWith("<configuration>")) {
202 newConfiguration = "<configuration>" + newConfiguration
203 + "</configuration>";
204 }
205 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
206 "encoding=\"UTF-8\"?>");
207 rpc.append("<rpc>");
208 rpc.append("<copy-config>");
209 rpc.append("<target>");
210 rpc.append("<" + targetConfiguration + "/>");
211 rpc.append("</target>");
212 rpc.append("<source>");
213 rpc.append("<" + newConfiguration + "/>");
214 rpc.append("</source>");
215 rpc.append("</copy-config>");
216 rpc.append("</rpc>");
217 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800218 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700219 }
220
221 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800222 public boolean deleteConfig(String targetConfiguration) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700223 if (targetConfiguration.equals("running")) {
224 log.warn("Target configuration for delete operation can't be \"running\"",
225 targetConfiguration);
226 return false;
227 }
228 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
229 "encoding=\"UTF-8\"?>");
230 rpc.append("<rpc>");
231 rpc.append("<delete-config>");
232 rpc.append("<target>");
233 rpc.append("<" + targetConfiguration + "/>");
234 rpc.append("</target>");
235 rpc.append("</delete-config>");
236 rpc.append("</rpc>");
237 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800238 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700239 }
240
241 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800242 public boolean lock() throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700243 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
244 "encoding=\"UTF-8\"?>");
245 rpc.append("<rpc>");
246 rpc.append("<lock>");
247 rpc.append("<target>");
248 rpc.append("<candidate/>");
249 rpc.append("</target>");
250 rpc.append("</lock>");
251 rpc.append("</rpc>");
252 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800253 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700254 }
255
256 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800257 public boolean unlock() throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700258 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
259 "encoding=\"UTF-8\"?>");
260 rpc.append("<rpc>");
261 rpc.append("<unlock>");
262 rpc.append("<target>");
263 rpc.append("<candidate/>");
264 rpc.append("</target>");
265 rpc.append("</unlock>");
266 rpc.append("</rpc>");
267 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800268 return checkReply(doRequest(rpc.toString()));
andreaeb70a942015-10-16 21:34:46 -0700269 }
270
271 @Override
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800272 public boolean close() throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700273 return close(false);
274 }
275
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800276 private boolean close(boolean force) throws IOException {
andreaeb70a942015-10-16 21:34:46 -0700277 StringBuilder rpc = new StringBuilder();
278 rpc.append("<rpc>");
279 if (force) {
280 rpc.append("<kill-configuration/>");
281 } else {
282 rpc.append("<close-configuration/>");
283 }
284 rpc.append("<close-configuration/>");
285 rpc.append("</rpc>");
286 rpc.append(endpattern);
Andrea Campanella1cd641b2015-12-07 17:28:34 -0800287 return checkReply(doRequest(rpc.toString())) || close(true);
andreaeb70a942015-10-16 21:34:46 -0700288 }
289
290 @Override
291 public String getSessionId() {
292 if (serverCapabilities.contains("<session-id>")) {
293 String[] outer = serverCapabilities.split("<session-id>");
294 Preconditions.checkArgument(outer.length != 1,
295 "Error in retrieving the session id");
296 String[] value = outer[1].split("</session-id>");
297 Preconditions.checkArgument(value.length != 1,
298 "Error in retrieving the session id");
299 return value[0];
300 } else {
301 return String.valueOf(-1);
302 }
303 }
304
305 @Override
306 public String getServerCapabilities() {
307 return serverCapabilities;
308 }
309
310 @Override
311 public void setDeviceCapabilities(List<String> capabilities) {
312 deviceCapabilities = capabilities;
313 }
314
315 private boolean checkReply(String reply) {
316 if (reply != null) {
317 if (!reply.contains("<rpc-error>")) {
318 return true;
319 } else if (reply.contains("<ok/>")
320 || (reply.contains("<rpc-error>")
321 && reply.contains("warning"))) {
322 return true;
323 }
324 }
325 return false;
326 }
327
328 private String readOne() throws IOException {
329 //TODO try a simple string
330 final StringWriter reply = new StringWriter();
331 while (true) {
332 int charRead = bufferReader.read();
333 if (charRead == -1) {
334 throw new IOException("Session closed");
335 }
336
337 for (int i = 0; i < endpattern.length(); i++) {
338 if (charRead == endpattern.charAt(i)) {
339 if (i < endpattern.length() - 1) {
340 charRead = bufferReader.read();
341 } else {
342 return reply.getBuffer().toString();
343 }
344 } else {
345 String s = endpattern.substring(0, i);
346 for (int j = 0; i < s.length(); j++) {
347 reply.write(s.charAt(j));
348 }
349 reply.write(charRead);
350 break;
351 }
352 }
353 }
354 }
355
356}