ONOS-7629 - minimal support for Ciena 51xx devices

Change-Id: I19408f558c1766686b8e567ae27e3077db782cf3
diff --git a/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/Ciena5162DriversLoader.java b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/Ciena5162DriversLoader.java
new file mode 100644
index 0000000..338c2f1
--- /dev/null
+++ b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/Ciena5162DriversLoader.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.ciena.c5162;
+
+import org.apache.felix.scr.annotations.Component;
+import org.onosproject.net.driver.AbstractDriverLoader;
+
+/**
+ * Loader for Ciena device drivers.
+ */
+@Component(immediate = true)
+public class Ciena5162DriversLoader extends AbstractDriverLoader {
+
+    public Ciena5162DriversLoader() {
+        super("/ciena-5162-drivers.xml");
+    }
+}
diff --git a/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/Ciena5162DeviceDescription.java b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/Ciena5162DeviceDescription.java
new file mode 100644
index 0000000..aeb0d11
--- /dev/null
+++ b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/Ciena5162DeviceDescription.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.ciena.c5162.netconf;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.onlab.packet.ChassisId;
+import org.onosproject.drivers.netconf.TemplateManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.LinkDiscovery;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceDescriptionDiscovery;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+import org.onosproject.netconf.NetconfController;
+import org.onosproject.netconf.NetconfException;
+import org.onosproject.netconf.NetconfSession;
+import org.slf4j.Logger;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Discovers the ports from a Ciena WaveServer Rest device.
+ */
+public class Ciena5162DeviceDescription extends AbstractHandlerBehaviour
+        implements DeviceDescriptionDiscovery, PortStatisticsDiscovery, LinkDiscovery {
+    private static final Logger log = getLogger(Ciena5162DeviceDescription.class);
+    private static final TemplateManager TEMPLATE_MANAGER = new TemplateManager();
+
+    static {
+        TEMPLATE_MANAGER.load(Ciena5162DeviceDescription.class, "/templates/requests/%s.j2", "systemInfo",
+                "softwareVersion", "logicalPorts", "port-stats", "link-info");
+    }
+
+    @Override
+    public DeviceDescription discoverDeviceDetails() {
+
+        DeviceId deviceId = handler().data().deviceId();
+        NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+        NetconfSession session = controller.getDevicesMap().get(handler().data().deviceId()).getSession();
+        try {
+            Node systemInfo = TEMPLATE_MANAGER.doRequest(session, "systemInfo");
+            Node softwareVersion = TEMPLATE_MANAGER.doRequest(session, "softwareVersion");
+            XPath xp = XPathFactory.newInstance().newXPath();
+            String mac = xp.evaluate("components/component/properties/property/state/value/text()", systemInfo)
+                    .toUpperCase();
+            return new DefaultDeviceDescription(deviceId.uri(), Device.Type.SWITCH,
+                    xp.evaluate("components/component/state/mfg-name/text()", systemInfo),
+                    xp.evaluate("components/component/state/name/text()", systemInfo),
+                    xp.evaluate("software-state/running-package/package-version/text()", softwareVersion),
+                    xp.evaluate("components/component/state/serial-no/text()", systemInfo),
+                    new ChassisId(Long.valueOf(mac, 16)));
+
+        } catch (XPathExpressionException | NetconfException ne) {
+            log.error("failed to query system info from device {}", handler().data().deviceId(), ne);
+        }
+
+        return new DefaultDeviceDescription(deviceId.uri(), Device.Type.SWITCH, "Ciena", "5162", "Unknown", "Unknown",
+                new ChassisId());
+    }
+
+    /**
+     * Convert the specification of port speed in the of of #unit, i.e. {@10G} to MB
+     * as represented by a Long.
+     *
+     * @param ps
+     *            specification of port speed
+     * @return port speed as MBs
+     */
+    private Long portSpeedToLong(String ps) {
+        String value = ps.trim();
+        StringBuilder digits = new StringBuilder();
+        String unit = "";
+        for (int i = 0; i < value.length(); i += 1) {
+            final char c = value.charAt(i);
+            if (Character.isDigit(c)) {
+                digits.append(c);
+            } else {
+                unit = value.substring(i).toUpperCase().trim();
+                break;
+            }
+        }
+
+        switch (unit) {
+        case "G":
+        case "GB":
+            return Long.valueOf(digits.toString()) * 1000;
+        case "M":
+        case "MB":
+        default:
+            return Long.valueOf(digits.toString());
+        }
+    }
+
+    @Override
+    public List<PortDescription> discoverPortDetails() {
+        List<PortDescription> ports = new ArrayList<PortDescription>();
+        DeviceId deviceId = handler().data().deviceId();
+        NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+        if (controller == null || controller.getDevicesMap() == null
+                || controller.getDevicesMap().get(deviceId) == null) {
+            log.warn("NETCONF session to device {} not yet established, will be retried", deviceId);
+            return ports;
+        }
+        NetconfSession session = controller.getDevicesMap().get(deviceId).getSession();
+
+        try {
+            Node logicalPorts = TEMPLATE_MANAGER.doRequest(session, "logicalPorts");
+            XPath xp = XPathFactory.newInstance().newXPath();
+            NodeList nl = (NodeList) xp.evaluate("interfaces/interface/config", logicalPorts, XPathConstants.NODESET);
+            int count = nl.getLength();
+            Node node;
+            for (int i = 0; i < count; i += 1) {
+                node = nl.item(i);
+                if (xp.evaluate("type/text()", node).equals("ettp")) {
+                    ports.add(DefaultPortDescription.builder()
+                            .withPortNumber(PortNumber.portNumber(xp.evaluate("name/text()", node)))
+                            .isEnabled(Boolean.valueOf(xp.evaluate("admin-status/text()", node)))
+                            .portSpeed(portSpeedToLong(xp.evaluate("port-speed/text()", node))).type(Port.Type.PACKET)
+                            .build());
+                }
+            }
+        } catch (NetconfException | XPathExpressionException e) {
+            log.error("Unable to retrieve port information for device {}, {}", deviceId, e);
+        }
+        return ports;
+    }
+
+    @Override
+    public Collection<PortStatistics> discoverPortStatistics() {
+        List<PortStatistics> stats = new ArrayList<PortStatistics>();
+
+        DeviceId deviceId = handler().data().deviceId();
+        NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+        if (controller == null || controller.getDevicesMap() == null
+                || controller.getDevicesMap().get(deviceId) == null) {
+            log.warn("NETCONF session to device {} not yet established, will be retried", deviceId);
+            return stats;
+        }
+        NetconfSession session = controller.getDevicesMap().get(deviceId).getSession();
+
+        try {
+            Node data = TEMPLATE_MANAGER.doRequest(session, "port-stats");
+            XPath xp = XPathFactory.newInstance().newXPath();
+            NodeList interfaces = (NodeList) xp.evaluate("interfaces/interface", data, XPathConstants.NODESET);
+            int count = interfaces.getLength();
+            for (int i = 0; i < count; i += 1) {
+                Node iface = interfaces.item(i);
+                if (xp.evaluate("config/type/text()", iface).equals("ettp")) {
+                    stats.add(DefaultPortStatistics.builder().setDeviceId(deviceId)
+                            .setPort(PortNumber.portNumber(xp.evaluate("name/text()", iface)))
+                            .setBytesReceived(Long.valueOf(xp.evaluate("state/counters/in-octets/text()", iface)))
+                            .setBytesSent(Long.valueOf(xp.evaluate("state/counters/out-octets/text()", iface)))
+                            .setPacketsReceived(Long.valueOf(xp.evaluate("state/counters/in-pkts/text()", iface)))
+                            .setPacketsSent(Long.valueOf(xp.evaluate("state/counters/out-pkts/text()", iface)))
+                            .setPacketsTxErrors(Long.valueOf(xp.evaluate("state/counters/out-errors/text()", iface)))
+                            .setPacketsRxErrors(Long.valueOf(xp.evaluate("state/counters/in-errors/text()", iface)))
+                            .build());
+                }
+            }
+        } catch (NetconfException | XPathExpressionException e) {
+            log.error("Unable to retrieve port statistics for device {}, {}", deviceId, e);
+        }
+
+        return stats;
+    }
+
+    @Override
+    public Set<LinkDescription> getLinks() {
+        log.debug("LINKS CHECKING ...");
+        Set<LinkDescription> links = new HashSet<LinkDescription>();
+        DeviceId deviceId = handler().data().deviceId();
+        NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+        if (controller == null || controller.getDevicesMap() == null
+                || controller.getDevicesMap().get(deviceId) == null) {
+            log.warn("NETCONF session to device {} not yet established, cannot load links, will be retried", deviceId);
+            return links;
+        }
+        NetconfSession session = controller.getDevicesMap().get(deviceId).getSession();
+        try {
+
+            DeviceService deviceService = this.handler().get(DeviceService.class);
+
+            Iterable<Device> devices = deviceService.getAvailableDevices();
+            Map<String, Device> lookup = new HashMap<String, Device>();
+            for (Device d : devices) {
+                lookup.put(d.chassisId().toString().toUpperCase(), d);
+            }
+
+            Node logicalPorts = TEMPLATE_MANAGER.doRequest(session, "link-info");
+            XPath xp = XPathFactory.newInstance().newXPath();
+            NodeList ifaces = (NodeList) xp.evaluate("interfaces/interface", logicalPorts, XPathConstants.NODESET);
+            int count = ifaces.getLength();
+            Node iface;
+            Node destChassis;
+            for (int i = 0; i < count; i += 1) {
+                iface = ifaces.item(i);
+                if (xp.evaluate("config/type/text()", iface).equals("ettp")) {
+                    destChassis = (Node) xp.evaluate("state/lldp-remote-port-operational/chassis-id", iface,
+                            XPathConstants.NODE);
+
+                    if (destChassis != null) {
+                        Device dest = lookup.get(destChassis.getTextContent().toUpperCase());
+
+                        if (dest != null) {
+
+                            links.add(new DefaultLinkDescription(
+                                    new ConnectPoint(deviceId,
+                                            PortNumber.portNumber(xp.evaluate("name/text()", iface))),
+                                    new ConnectPoint(dest.id(),
+                                            PortNumber.portNumber(xp.evaluate(
+                                                    "state/lldp-remote-port-operational/port-id/text()", iface))),
+                                    Link.Type.DIRECT, true));
+                        } else {
+                            log.error("DEST CHASSIS is NULL for {}", xp.evaluate("name/text()", iface));
+                        }
+                    } else {
+                        log.debug("NO LINK for {}", xp.evaluate("name/text()", iface));
+                    }
+                }
+            }
+        } catch (NetconfException | XPathExpressionException e) {
+            log.error("Unable to retrieve links for device {}, {}", deviceId, e);
+        }
+
+        return links;
+    }
+
+}
diff --git a/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/Ciena5162PortAdmin.java b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/Ciena5162PortAdmin.java
new file mode 100644
index 0000000..6fbf631
--- /dev/null
+++ b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/Ciena5162PortAdmin.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.ciena.c5162.netconf;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.onosproject.drivers.netconf.TemplateManager;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.PortAdmin;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.netconf.NetconfController;
+import org.onosproject.netconf.NetconfException;
+import org.onosproject.netconf.NetconfSession;
+import org.slf4j.Logger;
+import org.w3c.dom.Node;
+
+/**
+ * Handles port administration for Ciena 5162 devices using the NETCONF
+ * protocol.
+ */
+public class Ciena5162PortAdmin extends AbstractHandlerBehaviour implements PortAdmin {
+
+    private static final Logger log = getLogger(Ciena5162PortAdmin.class);
+    private static final TemplateManager TEMPLATE_MANAGER = new TemplateManager();
+
+    static {
+        TEMPLATE_MANAGER.load(Ciena5162PortAdmin.class, "/templates/requests/%s.j2", "logicalPort", "port-admin-state");
+    }
+
+    /**
+     * Sets the administrative state of the given port to the given value.
+     *
+     * @param number
+     *            port number
+     * @param value
+     *            state, true for enabled, false for disabled
+     * @return true if successfully set
+     */
+    private CompletableFuture<Boolean> setAdminState(PortNumber number, Boolean value) {
+        NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+        NetconfSession session = controller.getDevicesMap().get(handler().data().deviceId()).getSession();
+
+        try {
+            Map<String, Object> templateContext = new HashMap<String, Object>();
+            templateContext.put("port-number", number.toLong());
+            templateContext.put("admin-state", value.toString());
+            Node req = (Node) TEMPLATE_MANAGER.doRequest(session, "port-admin-state", templateContext, "/",
+                    XPathConstants.NODE);
+            XPath xp = XPathFactory.newInstance().newXPath();
+
+            // If OK element exists then it worked.
+            Node ok = (Node) xp.evaluate("/rpc-reply/ok", req, XPathConstants.NODE);
+            return CompletableFuture.completedFuture(ok != null);
+        } catch (XPathExpressionException | NetconfException e) {
+            log.error("Unable to set port admin state for port {} to {}", number, handler().data().deviceId(), value,
+                    e);
+        }
+        return CompletableFuture.completedFuture(false);
+
+    }
+
+    @Override
+    public CompletableFuture<Boolean> enable(PortNumber number) {
+        return setAdminState(number, true);
+    }
+
+    @Override
+    public CompletableFuture<Boolean> disable(PortNumber number) {
+        return setAdminState(number, false);
+    }
+
+    @Override
+    public CompletableFuture<Boolean> isEnabled(PortNumber number) {
+        NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+        NetconfSession session = controller.getDevicesMap().get(handler().data().deviceId()).getSession();
+
+        try {
+            Map<String, Object> templateContext = new HashMap<String, Object>();
+            templateContext.put("port-number", number.toString());
+            Node port = TEMPLATE_MANAGER.doRequest(session, "logicalPort", templateContext);
+            XPath xp = XPathFactory.newInstance().newXPath();
+            return CompletableFuture.completedFuture(Boolean.valueOf(xp.evaluate("admin-status/text()", port)));
+        } catch (XPathExpressionException | NetconfException e) {
+            log.error("Unable to query port state for port {} from device {}", number, handler().data().deviceId(), e);
+        }
+        return CompletableFuture.completedFuture(false);
+    }
+
+}
diff --git a/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/package-info.java b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/package-info.java
new file mode 100644
index 0000000..c83062b
--- /dev/null
+++ b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/netconf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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 for Ciena device drivers.
+ */
+package org.onosproject.drivers.ciena.c5162.netconf;
diff --git a/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/package-info.java b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/package-info.java
new file mode 100644
index 0000000..54ff1a2
--- /dev/null
+++ b/drivers/ciena/c5162/src/main/java/org/onosproject/drivers/ciena/c5162/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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 for Ciena device drivers.
+ */
+package org.onosproject.drivers.ciena.c5162;