blob: 8619abc09d48d8356826451298463a293734e2fc [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;
23import org.onosproject.netconf.NetconfSession;
24import org.slf4j.Logger;
25import org.slf4j.LoggerFactory;
26
27import java.io.BufferedReader;
28import java.io.IOException;
29import java.io.InputStreamReader;
30import java.io.PrintWriter;
31import java.io.StringWriter;
32import java.util.ArrayList;
33import java.util.Arrays;
34import 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;
53
54 private List<String> deviceCapabilities =
55 new ArrayList<>(
56 Arrays.asList("urn:ietf:params:netconf:base:1.0"));
57
58 private String serverCapabilities;
59 private String endpattern = "]]>]]>";
60
61
62 public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws IOException {
63 this.deviceInfo = deviceInfo;
64 connectionActive = false;
65 startConnection();
66 }
67
68
69 private void startConnection() throws IOException {
70 if (!connectionActive) {
71 netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
72 netconfConnection.connect(null, CONNECTION_TIMEOUT, 0);
73 boolean isAuthenticated;
74 try {
75 if (deviceInfo.getKeyFile() != null) {
76 isAuthenticated = netconfConnection.authenticateWithPublicKey(
77 deviceInfo.name(), deviceInfo.getKeyFile(),
78 deviceInfo.password());
79 } else {
80 log.info("authenticate with username {} and password {}",
81 deviceInfo.name(), deviceInfo.password());
82 isAuthenticated = netconfConnection.authenticateWithPassword(
83 deviceInfo.name(), deviceInfo.password());
84 }
85 } catch (IOException e) {
86 throw new IOException("Authentication connection failed:" +
87 e.getMessage());
88 }
89
90 connectionActive = true;
91 Preconditions.checkArgument(isAuthenticated,
92 "Authentication password and username failed");
93 startSshSession();
94 }
95 }
96
97 private void startSshSession() throws IOException {
98 try {
99 sshSession = netconfConnection.openSession();
100 sshSession.startSubSystem("netconf");
101 bufferReader = new BufferedReader(new InputStreamReader(
102 sshSession.getStdout()));
103 out = new PrintWriter(sshSession.getStdin());
104 sendHello();
105 } catch (IOException e) {
106 throw new IOException("Failed to create ch.ethz.ssh2.Session session:" +
107 e.getMessage());
108 }
109 }
110
111 private void sendHello() throws IOException {
112 serverCapabilities = doRequest(createHelloString());
113 }
114
115 private String createHelloString() {
116 StringBuilder hellobuffer = new StringBuilder();
117 hellobuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
118 hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
119 hellobuffer.append(" <capabilities>\n");
120 deviceCapabilities.forEach(
121 cap -> hellobuffer.append(" <capability>" + cap + "</capability>\n"));
122 hellobuffer.append(" </capabilities>\n");
123 hellobuffer.append("</hello>\n");
124 hellobuffer.append(endpattern);
125 return hellobuffer.toString();
126
127 }
128
129 @Override
130 public String doRPC(String request) {
131 String reply = "ERROR";
132 try {
133 reply = doRequest(request);
134 if (checkReply(reply)) {
135 return reply;
136 } else {
137 return "ERROR " + reply;
138 }
139 } catch (IOException e) {
140 log.error("Problem in the reading from the SSH connection " + e);
141 }
142 return reply;
143 }
144
145 private String doRequest(String request) throws IOException {
146 log.info("sshState " + sshSession.getState() + "request" + request);
147 if (sshSession.getState() != 2) {
148 try {
149 startSshSession();
150 } catch (IOException e) {
151 log.info("the connection had to be reopened");
152 startConnection();
153 }
154 sendHello();
155 }
156 log.info("sshState after" + sshSession.getState());
157 out.print(request);
158 out.flush();
159 messageID++;
160 return readOne();
161 }
162
163 @Override
164 public String get(String request) {
165 return doRPC(request);
166 }
167
168 @Override
169 public String getConfig(String targetConfiguration) {
170 return getConfig(targetConfiguration, null);
171 }
172
173 @Override
174 public String getConfig(String targetConfiguration, String configurationSchema) {
175 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
176 rpc.append("<rpc message-id=\"" + messageID + "\" "
177 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
178 rpc.append("<get-config>\n");
179 rpc.append("<source>\n");
180 rpc.append("<" + targetConfiguration + "/>");
181 rpc.append("</source>");
182 if (configurationSchema != null) {
183 rpc.append("<filter type=\"subtree\">\n");
184 rpc.append(configurationSchema + "\n");
185 rpc.append("</filter>\n");
186 }
187 rpc.append("</get-config>\n");
188 rpc.append("</rpc>\n");
189 rpc.append(endpattern);
190 String reply = null;
191 try {
192 reply = doRequest(rpc.toString());
193 } catch (IOException e) {
194 e.printStackTrace();
195 }
196
197 return checkReply(reply) ? reply : null;
198 }
199
200 @Override
201 public boolean editConfig(String newConfiguration) {
202 newConfiguration = newConfiguration + endpattern;
203 String reply = null;
204 try {
205 reply = doRequest(newConfiguration);
206 } catch (IOException e) {
207 e.printStackTrace();
208 }
209 return checkReply(reply);
210 }
211
212 @Override
213 public boolean copyConfig(String targetConfiguration, String newConfiguration) {
214 newConfiguration = newConfiguration.trim();
215 if (!newConfiguration.startsWith("<configuration>")) {
216 newConfiguration = "<configuration>" + newConfiguration
217 + "</configuration>";
218 }
219 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
220 "encoding=\"UTF-8\"?>");
221 rpc.append("<rpc>");
222 rpc.append("<copy-config>");
223 rpc.append("<target>");
224 rpc.append("<" + targetConfiguration + "/>");
225 rpc.append("</target>");
226 rpc.append("<source>");
227 rpc.append("<" + newConfiguration + "/>");
228 rpc.append("</source>");
229 rpc.append("</copy-config>");
230 rpc.append("</rpc>");
231 rpc.append(endpattern);
232 String reply = null;
233 try {
234 reply = doRequest(rpc.toString());
235 } catch (IOException e) {
236 e.printStackTrace();
237 }
238
239 return checkReply(reply);
240 }
241
242 @Override
243 public boolean deleteConfig(String targetConfiguration) {
244 if (targetConfiguration.equals("running")) {
245 log.warn("Target configuration for delete operation can't be \"running\"",
246 targetConfiguration);
247 return false;
248 }
249 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
250 "encoding=\"UTF-8\"?>");
251 rpc.append("<rpc>");
252 rpc.append("<delete-config>");
253 rpc.append("<target>");
254 rpc.append("<" + targetConfiguration + "/>");
255 rpc.append("</target>");
256 rpc.append("</delete-config>");
257 rpc.append("</rpc>");
258 rpc.append(endpattern);
259 String reply = null;
260 try {
261 reply = doRequest(rpc.toString());
262 } catch (IOException e) {
263 e.printStackTrace();
264 }
265
266 return checkReply(reply);
267 }
268
269 @Override
270 public boolean lock() {
271 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
272 "encoding=\"UTF-8\"?>");
273 rpc.append("<rpc>");
274 rpc.append("<lock>");
275 rpc.append("<target>");
276 rpc.append("<candidate/>");
277 rpc.append("</target>");
278 rpc.append("</lock>");
279 rpc.append("</rpc>");
280 rpc.append(endpattern);
281 String reply = null;
282 try {
283 reply = doRequest(rpc.toString());
284 } catch (IOException e) {
285 e.printStackTrace();
286 }
287 return checkReply(reply);
288 }
289
290 @Override
291 public boolean unlock() {
292 StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
293 "encoding=\"UTF-8\"?>");
294 rpc.append("<rpc>");
295 rpc.append("<unlock>");
296 rpc.append("<target>");
297 rpc.append("<candidate/>");
298 rpc.append("</target>");
299 rpc.append("</unlock>");
300 rpc.append("</rpc>");
301 rpc.append(endpattern);
302 String reply = null;
303 try {
304 reply = doRequest(rpc.toString());
305 } catch (IOException e) {
306 e.printStackTrace();
307 }
308 return checkReply(reply);
309 }
310
311 @Override
312 public boolean close() {
313 return close(false);
314 }
315
316 private boolean close(boolean force) {
317 StringBuilder rpc = new StringBuilder();
318 rpc.append("<rpc>");
319 if (force) {
320 rpc.append("<kill-configuration/>");
321 } else {
322 rpc.append("<close-configuration/>");
323 }
324 rpc.append("<close-configuration/>");
325 rpc.append("</rpc>");
326 rpc.append(endpattern);
327 return checkReply(rpc.toString()) ? true : close(true);
328 }
329
330 @Override
331 public String getSessionId() {
332 if (serverCapabilities.contains("<session-id>")) {
333 String[] outer = serverCapabilities.split("<session-id>");
334 Preconditions.checkArgument(outer.length != 1,
335 "Error in retrieving the session id");
336 String[] value = outer[1].split("</session-id>");
337 Preconditions.checkArgument(value.length != 1,
338 "Error in retrieving the session id");
339 return value[0];
340 } else {
341 return String.valueOf(-1);
342 }
343 }
344
345 @Override
346 public String getServerCapabilities() {
347 return serverCapabilities;
348 }
349
350 @Override
351 public void setDeviceCapabilities(List<String> capabilities) {
352 deviceCapabilities = capabilities;
353 }
354
355 private boolean checkReply(String reply) {
356 if (reply != null) {
357 if (!reply.contains("<rpc-error>")) {
358 return true;
359 } else if (reply.contains("<ok/>")
360 || (reply.contains("<rpc-error>")
361 && reply.contains("warning"))) {
362 return true;
363 }
364 }
365 return false;
366 }
367
368 private String readOne() throws IOException {
369 //TODO try a simple string
370 final StringWriter reply = new StringWriter();
371 while (true) {
372 int charRead = bufferReader.read();
373 if (charRead == -1) {
374 throw new IOException("Session closed");
375 }
376
377 for (int i = 0; i < endpattern.length(); i++) {
378 if (charRead == endpattern.charAt(i)) {
379 if (i < endpattern.length() - 1) {
380 charRead = bufferReader.read();
381 } else {
382 return reply.getBuffer().toString();
383 }
384 } else {
385 String s = endpattern.substring(0, i);
386 for (int j = 0; i < s.length(); j++) {
387 reply.write(s.charAt(j));
388 }
389 reply.write(charRead);
390 break;
391 }
392 }
393 }
394 }
395
396}