b3d26b062d0a5196490664010026a3cddd99b150
[onosfw.git] /
1 /*
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 package org.onosproject.provider.netconf.device.impl;
17
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static org.onlab.util.Tools.delay;
20 import static org.slf4j.LoggerFactory.getLogger;
21
22 import java.io.IOException;
23 import java.io.StringReader;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import org.jdom2.Document;
28 import org.jdom2.Element;
29 import org.jdom2.input.SAXBuilder;
30 import org.jdom2.output.Format;
31 import org.jdom2.output.XMLOutputter;
32 import org.slf4j.Logger;
33
34 import com.tailf.jnc.Capabilities;
35 import com.tailf.jnc.JNCException;
36 import com.tailf.jnc.SSHConnection;
37 import com.tailf.jnc.SSHSession;
38
39 /**
40  * This is a logical representation of actual NETCONF device, carrying all the
41  * necessary information to connect and execute NETCONF operations.
42  */
43 public class NetconfDevice {
44     private final Logger log = getLogger(NetconfDevice.class);
45
46     /**
47      * The Device State is used to determine whether the device is active or
48      * inactive. This state infomation will help Device Creator to add or delete
49      * the device from the core.
50      */
51     public static enum DeviceState {
52         /* Used to specify Active state of the device */
53         ACTIVE,
54         /* Used to specify inactive state of the device */
55         INACTIVE,
56         /* Used to specify invalid state of the device */
57         INVALID
58     }
59
60     private static final int DEFAULT_SSH_PORT = 22;
61     private static final int DEFAULT_CON_TIMEOUT = 0;
62     private static final String XML_CAPABILITY_KEY = "capability";
63     private static final int EVENTINTERVAL = 2000;
64     private static final int CONNECTION_CHECK_INTERVAL = 3;
65     private static final String INPUT_HELLO_XML_MSG = new StringBuilder(
66                                                                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
67             .append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">")
68             .append("<capabilities><capability>urn:ietf:params:netconf:base:1.0</capability>")
69             .append("</capabilities></hello>").toString();
70
71     private String sshHost;
72     private int sshPort = DEFAULT_SSH_PORT;
73     private int connectTimeout = DEFAULT_CON_TIMEOUT;
74     private String username;
75     private String password;
76     private boolean reachable = false;
77
78     private List<String> capabilities = new ArrayList<String>();
79     private SSHConnection sshConnection = null;
80
81     private DeviceState deviceState = DeviceState.INVALID;
82
83     protected NetconfDevice(String sshHost, int sshPort, String username,
84                             String password) {
85         this.username = checkNotNull(username,
86                                      "Netconf Username Cannot be null");
87         this.sshHost = checkNotNull(sshHost, "Netconf Device IP cannot be null");
88         this.sshPort = checkNotNull(sshPort,
89                                     "Netconf Device SSH port cannot be null");
90         this.password = password;
91     }
92
93     /**
94      * This will try to connect to NETCONF device and find all the capabilities.
95      *
96      * @throws Exception if unable to connect to the device
97      */
98     // FIXME: this should not be a generic Exception; perhaps wrap in some RuntimeException
99     public void init() throws Exception {
100         try {
101             if (sshConnection == null) {
102                 sshConnection = new SSHConnection(sshHost, sshPort, connectTimeout);
103                 sshConnection.authenticateWithPassword(username, password);
104             }
105             // Send hello message to retrieve capabilities.
106         } catch (IOException e) {
107             log.error("Fatal Error while creating connection to the device: "
108                     + deviceInfo(), e);
109             throw e;
110         } catch (JNCException e) {
111             log.error("Failed to connect to the device: " + deviceInfo(), e);
112             throw e;
113         }
114
115         hello();
116     }
117
118     private void hello() {
119         SSHSession ssh = null;
120         try {
121             ssh = new SSHSession(sshConnection);
122             String helloRequestXML = INPUT_HELLO_XML_MSG.trim();
123
124             log.debug("++++++++++++++++++++++++++++++++++Sending Hello: "
125                     + sshConnection.getGanymedConnection().getHostname()
126                     + "++++++++++++++++++++++++++++++++++");
127             printPrettyXML(helloRequestXML);
128             ssh.print(helloRequestXML);
129             // ssh.print(endCharSeq);
130             ssh.flush();
131             String xmlResponse = null;
132             int i = CONNECTION_CHECK_INTERVAL;
133             while (!ssh.ready() && i > 0) {
134                 delay(EVENTINTERVAL);
135                 i--;
136             }
137
138             if (ssh.ready()) {
139                 StringBuffer readOne = ssh.readOne();
140                 if (readOne == null) {
141                     log.error("The Hello Contains No Capabilites");
142                     throw new JNCException(
143                                            JNCException.SESSION_ERROR,
144                                            "server does not support NETCONF base capability: "
145                                                    + Capabilities.NETCONF_BASE_CAPABILITY);
146                 } else {
147                     xmlResponse = readOne.toString().trim();
148
149                     log.debug("++++++++++++++++++++++++++++++++++Reading Capabilities: "
150                             + sshConnection.getGanymedConnection()
151                                     .getHostname()
152                             + "++++++++++++++++++++++++++++++++++");
153
154                     printPrettyXML(xmlResponse);
155                     processCapabilities(xmlResponse);
156                 }
157             }
158             reachable = true;
159         } catch (IOException e) {
160             log.error("Fatal Error while sending Hello Message to the device: "
161                     + deviceInfo(), e);
162         } catch (JNCException e) {
163             log.error("Fatal Error while sending Hello Message to the device: "
164                     + deviceInfo(), e);
165         } finally {
166             log.debug("Closing the session after successful execution");
167             if (ssh != null) {
168                 ssh.close();
169             }
170         }
171     }
172
173     private void processCapabilities(String xmlResponse) throws JNCException {
174         if (xmlResponse.isEmpty()) {
175             log.error("The capability response cannot be empty");
176             throw new JNCException(
177                                    JNCException.SESSION_ERROR,
178                                    "server does not support NETCONF base capability: "
179                                            + Capabilities.NETCONF_BASE_CAPABILITY);
180         }
181         try {
182             Document doc = new SAXBuilder()
183                     .build(new StringReader(xmlResponse));
184             Element rootElement = doc.getRootElement();
185             processCapabilities(rootElement);
186         } catch (Exception e) {
187             log.error("ERROR while parsing the XML " + xmlResponse);
188         }
189     }
190
191     private void processCapabilities(Element rootElement) {
192         List<Element> children = rootElement.getChildren();
193         if (children.isEmpty()) {
194             return;
195         }
196         for (Element child : children) {
197
198             if (child.getName().equals(XML_CAPABILITY_KEY)) {
199                 capabilities.add(child.getValue());
200             }
201             if (!child.getChildren().isEmpty()) {
202                 processCapabilities(child);
203             }
204         }
205     }
206
207     private void printPrettyXML(String xmlstring) {
208         try {
209             Document doc = new SAXBuilder().build(new StringReader(xmlstring));
210             XMLOutputter xmOut = new XMLOutputter(Format.getPrettyFormat());
211             String outputString = xmOut.outputString(doc);
212             log.debug(outputString);
213         } catch (Exception e) {
214             log.error("ERROR while parsing the XML " + xmlstring, e);
215
216         }
217     }
218
219     /**
220      * This would return host IP and host Port, used by this particular Netconf
221      * Device.
222      * @return Device Information.
223      */
224     public String deviceInfo() {
225         return new StringBuilder("host: ").append(sshHost).append(". port: ")
226                 .append(sshPort).toString();
227     }
228
229     /**
230      * This will terminate the device connection.
231      */
232     public void disconnect() {
233         sshConnection.close();
234         reachable = false;
235     }
236
237     /**
238      * This will list down all the capabilities supported on the device.
239      * @return Capability list.
240      */
241     public List<String> getCapabilities() {
242         return capabilities;
243     }
244
245     /**
246      * This api is intended to know whether the device is connected or not.
247      * @return true if connected
248      */
249     public boolean isReachable() {
250         return reachable;
251     }
252
253     /**
254      * This will return the IP used connect ssh on the device.
255      * @return Netconf Device IP
256      */
257     public String getSshHost() {
258         return sshHost;
259     }
260
261     /**
262      * This will return the SSH Port used connect the device.
263      * @return SSH Port number
264      */
265     public int getSshPort() {
266         return sshPort;
267     }
268
269     /**
270      * The usename used to connect Netconf Device.
271      * @return Device Username
272      */
273     public String getUsername() {
274         return username;
275     }
276
277     /**
278      * Retrieve current state of the device.
279      * @return Current Device State
280      */
281     public DeviceState getDeviceState() {
282         return deviceState;
283     }
284
285     /**
286      * This is set the state information for the device.
287      * @param deviceState Next Device State
288      */
289     public void setDeviceState(DeviceState deviceState) {
290         this.deviceState = deviceState;
291     }
292
293     /**
294      * Check whether the device is in Active state.
295      * @return true if the device is Active
296      */
297     public boolean isActive() {
298         return deviceState == DeviceState.ACTIVE ? true : false;
299     }
300
301     public void setConnectTimeout(int connectTimeout) {
302         this.connectTimeout = connectTimeout;
303     }
304 }