Netconf Device Provider Plugin to discover and monitor NETCONF supported Devices.

Change-Id: I6d32c966fd4e9c3581db8285e2d712b6bffdb65c
diff --git a/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java
new file mode 100644
index 0000000..c1208fb
--- /dev/null
+++ b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.provider.netconf.device.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.delay;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.input.SAXBuilder;
+import org.jdom2.output.Format;
+import org.jdom2.output.XMLOutputter;
+import org.slf4j.Logger;
+
+import com.tailf.jnc.Capabilities;
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.SSHConnection;
+import com.tailf.jnc.SSHSession;
+
+/**
+ * This is a logical representation of actual NETCONF device, carrying all the
+ * necessary information to connect and execute NETCONF operations.
+ */
+public class NetconfDevice {
+    private static final Logger log = getLogger(NetconfDevice.class);
+
+    /**
+     * The Device State is used to determine whether the device is active or
+     * inactive. This state infomation will help Device Creator to add or delete
+     * the device from the core.
+     */
+    public static enum DeviceState {
+        /* Used to specify Active state of the device */
+        ACTIVE,
+        /* Used to specify In Active state of the device */
+        INACTIVE,
+        /* Used to specify In Valid state of the device */
+        INVALID
+    }
+
+    private static final int DEFAULT_SSH_PORT = 22;
+    private static final String XML_CAPABILITY_KEY = "capability";
+    private static final int EVENTINTERVAL = 2000;
+    private static final int CONNECTION_CHECK_INTERVAL = 3;
+    private static final String INPUT_HELLO_XML_MSG = new StringBuilder(
+                                                                        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
+            .append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">")
+            .append("<capabilities><capability>urn:ietf:params:netconf:base:1.0</capability>")
+            .append("</capabilities></hello>").toString();
+
+    private String sshHost;
+    private int sshPort = DEFAULT_SSH_PORT;
+    private String username;
+    private String password;
+    private boolean reachable = false;
+
+    private List<String> capabilities = new ArrayList<String>();
+    private SSHConnection sshConnection = null;
+
+    private DeviceState deviceState = DeviceState.INVALID;
+
+    protected NetconfDevice(String sshHost, int sshPort, String username,
+                            String password) {
+        this.username = checkNotNull(username,
+                                     "Netconf Username Cannot be null");
+        this.sshHost = checkNotNull(sshHost, "Netconf Device IP cannot be null");
+        this.sshPort = checkNotNull(sshPort,
+                                    "Netconf Device SSH port cannot be null");
+        this.password = password;
+    }
+
+    /**
+     * This will try to connect to NETCONF device and find all the capabilities.
+     */
+    public void init() throws Exception {
+        try {
+            if (sshConnection == null) {
+                sshConnection = new SSHConnection(sshHost, sshPort);
+                sshConnection.authenticateWithPassword(username, password);
+            }
+            // Send hello message to retrieve capabilities.
+        } catch (IOException e) {
+            NetconfDevice.log
+                    .error("Fatal Error while creating connection to the device: "
+                                   + deviceInfo(), e);
+            throw e;
+        } catch (JNCException e) {
+            NetconfDevice.log.error("Failed to connect to the device: "
+                    + deviceInfo(), e);
+            throw e;
+        }
+
+        hello();
+    }
+
+    private void hello() {
+        SSHSession ssh = null;
+        try {
+            ssh = new SSHSession(sshConnection);
+            String helloRequestXML = INPUT_HELLO_XML_MSG.trim();
+
+            if (NetconfDevice.log.isDebugEnabled()) {
+                NetconfDevice.log
+                        .debug("++++++++++++++++++++++++++++++++++Sending Hello: "
+                                + sshConnection.getGanymedConnection()
+                                        .getHostname()
+                                + "++++++++++++++++++++++++++++++++++");
+                printPrettyXML(helloRequestXML);
+            }
+            ssh.print(helloRequestXML);
+            // ssh.print(endCharSeq);
+            ssh.flush();
+            String xmlResponse = null;
+            int i = CONNECTION_CHECK_INTERVAL;
+            while (!ssh.ready() && i > 0) {
+                delay(EVENTINTERVAL);
+                i--;
+            }
+
+            if (ssh.ready()) {
+                StringBuffer readOne = ssh.readOne();
+                if (readOne == null) {
+                    NetconfDevice.log
+                            .error("The Hello Contains No Capabilites");
+                    throw new JNCException(
+                                           JNCException.SESSION_ERROR,
+                                           "server does not support NETCONF base capability: "
+                                                   + Capabilities.NETCONF_BASE_CAPABILITY);
+                } else {
+                    xmlResponse = readOne.toString().trim();
+
+                    if (NetconfDevice.log.isDebugEnabled()) {
+                        NetconfDevice.log
+                                .debug("++++++++++++++++++++++++++++++++++Reading Capabilities: "
+                                        + sshConnection.getGanymedConnection()
+                                                .getHostname()
+                                        + "++++++++++++++++++++++++++++++++++");
+
+                        printPrettyXML(xmlResponse);
+                    }
+                    processCapabilities(xmlResponse);
+                }
+            }
+            reachable = true;
+        } catch (IOException e) {
+            NetconfDevice.log
+                    .error("Fatal Error while sending Hello Message to the device: "
+                                   + deviceInfo(), e);
+        } catch (JNCException e) {
+            NetconfDevice.log
+                    .error("Fatal Error while sending Hello Message to the device: "
+                                   + deviceInfo(), e);
+        } finally {
+            if (NetconfDevice.log.isDebugEnabled()) {
+                NetconfDevice.log
+                        .debug("Closing the session after successful execution");
+            }
+            ssh.close();
+        }
+    }
+
+    private void processCapabilities(String xmlResponse) throws JNCException {
+        if (xmlResponse.isEmpty()) {
+            NetconfDevice.log.error("The capability response cannot be empty");
+            throw new JNCException(
+                                   JNCException.SESSION_ERROR,
+                                   "server does not support NETCONF base capability: "
+                                           + Capabilities.NETCONF_BASE_CAPABILITY);
+        }
+        try {
+            Document doc = new SAXBuilder()
+                    .build(new StringReader(xmlResponse));
+            Element rootElement = doc.getRootElement();
+            processCapabilities(rootElement);
+        } catch (Exception e) {
+            NetconfDevice.log.error("ERROR while parsing the XML "
+                    + xmlResponse);
+        }
+    }
+
+    private void processCapabilities(Element rootElement) {
+        List<Element> children = rootElement.getChildren();
+        if (children.isEmpty()) {
+            return;
+        }
+        for (Element child : children) {
+
+            if (child.getName().equals(XML_CAPABILITY_KEY)) {
+                capabilities.add(child.getValue());
+            }
+            if (!child.getChildren().isEmpty()) {
+                processCapabilities(child);
+            }
+        }
+    }
+
+    private void printPrettyXML(String xmlstring) {
+        try {
+            Document doc = new SAXBuilder().build(new StringReader(xmlstring));
+            XMLOutputter xmOut = new XMLOutputter(Format.getPrettyFormat());
+            String outputString = xmOut.outputString(doc);
+            if (NetconfDevice.log.isDebugEnabled()) {
+                NetconfDevice.log.debug(outputString);
+            }
+        } catch (Exception e) {
+            NetconfDevice.log.error("ERROR while parsing the XML " + xmlstring,
+                                    e);
+
+        }
+    }
+
+    /**
+     * This would return host IP and host Port, used by this particular Netconf
+     * Device.
+     * @return Device Information.
+     */
+    public String deviceInfo() {
+        return new StringBuilder("host: ").append(sshHost).append(". port: ")
+                .append(sshPort).toString();
+    }
+
+    /**
+     * This will terminate the device connection.
+     */
+    public void disconnect() {
+        sshConnection.close();
+        reachable = false;
+    }
+
+    /**
+     * This will list down all the capabilities supported on the device.
+     * @return Capability list.
+     */
+    public List<String> getCapabilities() {
+        return capabilities;
+    }
+
+    /**
+     * This api is intended to know whether the device is connected or not.
+     * @return true if connected
+     */
+    public boolean isReachable() {
+        return reachable;
+    }
+
+    /**
+     * This will return the IP used connect ssh on the device.
+     * @return Netconf Device IP
+     */
+    public String getSshHost() {
+        return sshHost;
+    }
+
+    /**
+     * This will return the SSH Port used connect the device.
+     * @return SSH Port number
+     */
+    public int getSshPort() {
+        return sshPort;
+    }
+
+    /**
+     * The usename used to connect Netconf Device.
+     * @return Device Username
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Retrieve current state of the device.
+     * @return Current Device State
+     */
+    public DeviceState getDeviceState() {
+        return deviceState;
+    }
+
+    /**
+     * This is set the state information for the device.
+     * @param deviceState Next Device State
+     */
+    public void setDeviceState(DeviceState deviceState) {
+        this.deviceState = deviceState;
+    }
+
+    /**
+     * Check whether the device is in Active state.
+     * @return true if the device is Active
+     */
+    public boolean isActive() {
+        return deviceState == DeviceState.ACTIVE ? true : false;
+    }
+}