Oplink APS behaviour for NETCONF

Change-Id: I54dbbb4c0c946b7afc1bee9f811049e07a5dd502
diff --git a/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkNetconfUtility.java b/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkNetconfUtility.java
index f474a5c..f5223e2 100644
--- a/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkNetconfUtility.java
+++ b/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkNetconfUtility.java
@@ -16,14 +16,17 @@
 
 package org.onosproject.drivers.oplink;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.commons.configuration.HierarchicalConfiguration;
 import org.onosproject.drivers.utilities.XmlConfigParser;
 import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.netconf.DatastoreId;
 import org.onosproject.netconf.NetconfController;
 import org.onosproject.netconf.NetconfException;
 import org.onosproject.netconf.NetconfSession;
 
 import java.io.IOException;
+import java.util.List;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -33,7 +36,6 @@
 public final class OplinkNetconfUtility {
 
     // public used nodes
-    public static final String CFG_TAR_RUNNING = "running";
     public static final String CFG_MODE_MERGE = "merge";
     public static final String CFG_MODE_REMOVE = "remove";
     public static final String KEY_XMLNS = "xmlns=\"http://com/att/device\"";
@@ -54,6 +56,25 @@
     }
 
     /**
+     * Retrieves session reply information for get operation.
+     *
+     * @param handler parent driver handler
+     * @param filter the filter string of xml content
+     * @return the reply string
+     */
+    public static String netconfGet(DriverHandler handler, String filter) {
+        NetconfController controller = checkNotNull(handler.get(NetconfController.class));
+        NetconfSession session = controller.getNetconfDevice(handler.data().deviceId()).getSession();
+        String reply;
+        try {
+            reply = session.get(filter, null);
+        } catch (IOException e) {
+            throw new RuntimeException(new NetconfException("Failed to retrieve configuration.", e));
+        }
+        return reply;
+    }
+
+    /**
      * Retrieves session reply information for get config operation.
      *
      * @param handler parent driver handler
@@ -65,7 +86,7 @@
         NetconfSession session = controller.getNetconfDevice(handler.data().deviceId()).getSession();
         String reply;
         try {
-            reply = session.getConfig(CFG_TAR_RUNNING, filter);
+            reply = session.getConfig(DatastoreId.RUNNING, filter);
         } catch (IOException e) {
             throw new RuntimeException(new NetconfException("Failed to retrieve configuration.", e));
         }
@@ -85,7 +106,7 @@
         NetconfSession session = controller.getNetconfDevice(handler.data().deviceId()).getSession();
         boolean reply = false;
         try {
-            reply = session.editConfig(CFG_TAR_RUNNING, mode, cfg);
+            reply = session.editConfig(DatastoreId.RUNNING, mode, cfg);
         } catch (IOException e) {
             throw new RuntimeException(new NetconfException("Failed to edit configuration.", e));
         }
@@ -97,12 +118,12 @@
      *
      * @param content the xml information
      * @param key the configuration key node
-     * @return the hierarchical configuration
+     * @return the hierarchical configuration, null if exception happens
      */
     public static HierarchicalConfiguration configAt(String content, String key) {
-        HierarchicalConfiguration cfg = XmlConfigParser.loadXmlString(content);
         HierarchicalConfiguration info;
         try {
+            HierarchicalConfiguration cfg = XmlConfigParser.loadXmlString(content);
             info = cfg.configurationAt(key);
         } catch (Exception e) {
             // Accept null for information polling
@@ -112,6 +133,25 @@
     }
 
     /**
+     * Retrieves specified node hierarchical configurations from the xml information.
+     *
+     * @param content the xml information
+     * @param key the configuration key node
+     * @return the hierarchical configurations, empty if exception happens
+     */
+    public static List<HierarchicalConfiguration> configsAt(String content, String key) {
+        List<HierarchicalConfiguration> info;
+        try {
+            HierarchicalConfiguration cfg = XmlConfigParser.loadXmlString(content);
+            info = cfg.configurationsAt(key);
+        } catch (Exception e) {
+            // Accept empty for information polling
+            return ImmutableList.of();
+        }
+        return info;
+    }
+
+    /**
      * Makes a xml format sentence.
      *
      * @param node the node name
diff --git a/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkOpticalProtectionSwitchConfig.java b/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkOpticalProtectionSwitchConfig.java
new file mode 100644
index 0000000..22460c1
--- /dev/null
+++ b/drivers/oplink/src/main/java/org/onosproject/drivers/oplink/OplinkOpticalProtectionSwitchConfig.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2016 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.drivers.oplink;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription;
+import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointState;
+import org.onosproject.net.behaviour.protection.ProtectionConfigBehaviour;
+import org.onosproject.net.behaviour.protection.TransportEndpointDescription;
+import org.onosproject.net.behaviour.protection.TransportEndpointId;
+import org.onosproject.net.behaviour.protection.TransportEndpointState;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.BasicLinkConfig;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.link.LinkService;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import static org.onosproject.drivers.oplink.OplinkNetconfUtility.*;
+import static org.onosproject.net.LinkKey.linkKey;
+import static org.onosproject.net.optical.OpticalAnnotations.INPUT_PORT_STATUS;
+import static org.onosproject.net.optical.OpticalAnnotations.STATUS_IN_SERVICE;
+import static org.onosproject.net.optical.OpticalAnnotations.STATUS_OUT_SERVICE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementations of the protection behaviours for Oplink Optical Protection Switch (OPS).
+ *             _____________________
+ *            |                     |     4
+ *   CLIENT RX|          ---50%-----|----------- PRIMARY TX
+ * -----------|----------           |
+ *      1     |          ---50%-----|----------- SECONDARY TX
+ *            |                     |     6
+ *            |    OPLINK OPS BOX   |
+ *            |     ___________     |     3
+ *   CLIENT TX|    |           |----|----------- PRIMARY RX
+ * -----------|----|   SWITCH  |    |
+ *      2     |    |___________|----|----------- SECONDARY RX
+ *            |_____________________|     5
+ *
+ *   - Oplink OPS has 6 uni-directional physical ports:
+ *      - port 2 is connected to the client side port.
+ *      - port 3 is primary port for network side.
+ *      - port 5 is secondary port for network side.
+ *      - port 1 directly connects to port 4 and 5 with 50/50 split light.
+ *   - Traffic protection
+ *      - Traffic(Optical light) from client port is broadcasted (50/50 split) to
+ *        both primary and secondary ports all the time.
+ *      - In fault free condition, traffic from primary port is bridged to client port and
+ *        in the case of primary port fails (LOS), traffic is bridged from secondary port to client port.
+ *      - User initiated switch (to primary or secondary) is also supported.
+ */
+public class OplinkOpticalProtectionSwitchConfig extends AbstractHandlerBehaviour
+        implements ProtectionConfigBehaviour {
+
+    // key nodes
+    private static final String KEY_CONFIG = "config";
+    private static final String KEY_OPSCONFIG = "ops-config";
+    private static final String KEY_STATE = "state";
+    private static final String KEY_OPSSTATE = "ops-state";
+    private static final String KEY_NAME_PRIMARY = "primary";
+    private static final String KEY_NAME_SECONDARY = "secondary";
+    private static final String KEY_OPT_FORCE = "force";
+    private static final String KEY_OPT_MANUAL = "manual";
+    private static final String KEY_OPT_AUTO = "auto-revertive";
+
+    // operation format: [OPT]-[NAME], eg. force-primary
+    private static final String FMT_OPT = "%s-%s";
+
+    // define virtual port number
+    private static final PortNumber PORT_VIRTUAL = PortNumber.portNumber(0);
+    private static final PortNumber PORT_PRIMARY = PortNumber.portNumber(3, "primary_port");
+    private static final PortNumber PORT_SECONDARY = PortNumber.portNumber(5, "secondary_port");
+    // log
+    private static final Logger log = getLogger(OplinkOpticalProtectionSwitchConfig.class);
+
+    @Override
+    public CompletableFuture<ConnectPoint> createProtectionEndpoint(
+            ProtectedTransportEndpointDescription configuration) {
+        // Add a virtual link between two virtual ports of the device and peer.
+        addLink(getPeerId());
+        // Only support one group in the device.
+        return CompletableFuture.completedFuture(new ConnectPoint(data().deviceId(), PORT_VIRTUAL));
+    }
+
+    @Override
+    public CompletableFuture<ConnectPoint> updateProtectionEndpoint(ConnectPoint identifier,
+            ProtectedTransportEndpointDescription configuration) {
+        // The function of updating protection virtual Port is not supported by the device.
+        return CompletableFuture.completedFuture(identifier);
+    }
+
+    @Override
+    public CompletableFuture<Boolean> deleteProtectionEndpoint(ConnectPoint identifier) {
+        if (identifier.port().equals(PORT_VIRTUAL)) {
+            // Remove the virtual link from peer to the virtual port.
+            removeLink(getPeerId());
+            return CompletableFuture.completedFuture(true);
+        }
+        return CompletableFuture.completedFuture(false);
+    }
+
+    @Override
+    public CompletableFuture<Map<ConnectPoint, ProtectedTransportEndpointDescription>>
+        getProtectionEndpointConfigs() {
+        // There are two underlying transport entity endpoints in the device.
+        Map<ConnectPoint, ProtectedTransportEndpointDescription> map = new HashMap<>();
+        map.put(new ConnectPoint(data().deviceId(), PORT_VIRTUAL), buildProtectedTransportEndpointDescription());
+        return CompletableFuture.completedFuture(map);
+    }
+
+    @Override
+    public CompletableFuture<Map<ConnectPoint, ProtectedTransportEndpointState>> getProtectionEndpointStates() {
+        Map<ConnectPoint, ProtectedTransportEndpointState> map = new HashMap<>();
+        map.put(new ConnectPoint(data().deviceId(), PORT_VIRTUAL), buildProtectedTransportEndpointState());
+        return CompletableFuture.completedFuture(map);
+    }
+
+    @Override
+    public CompletableFuture<Void> switchWorkingPath(ConnectPoint identifier, int index) {
+        return switchToManual(identifier, index);
+    }
+
+    @Override
+    public CompletableFuture<Void> switchToForce(ConnectPoint identifier, int index) {
+        return getProtectionEndpointConfig(identifier)
+                .thenApply(m -> m.paths().get(index))
+                .thenApply(m -> switchDevice(formatOperation(m.output().connectPoint().port(), KEY_OPT_FORCE)))
+                .thenApply(m -> null);
+    }
+
+    @Override
+    public CompletableFuture<Void> switchToManual(ConnectPoint identifier, int index) {
+        return getProtectionEndpointConfig(identifier)
+                .thenApply(m -> m.paths().get(index))
+                .thenApply(m -> switchDevice(formatOperation(m.output().connectPoint().port(), KEY_OPT_MANUAL)))
+                .thenApply(m -> null);
+    }
+
+    @Override
+    public CompletableFuture<Void> switchToAutomatic(ConnectPoint identifier) {
+        switchDevice(KEY_OPT_AUTO);
+        return CompletableFuture.completedFuture(null);
+    }
+
+    private ProtectedTransportEndpointState buildProtectedTransportEndpointState() {
+        // First, get active port from device.
+        PortNumber activePort = acquireActivePort();
+        // Build all endpoint state with port working attribute.
+        List<TransportEndpointState> states = new ArrayList<>();
+        states.add(buildTransportEndpointState(data().deviceId(), PORT_PRIMARY, activePort));
+        states.add(buildTransportEndpointState(data().deviceId(), PORT_SECONDARY, activePort));
+        return ProtectedTransportEndpointState.builder()
+                .withPathStates(states)
+                .withDescription(buildProtectedTransportEndpointDescription())
+                .withActivePathIndex(getActiveIndex(states, activePort))
+                .build();
+    }
+
+    private ProtectedTransportEndpointDescription buildProtectedTransportEndpointDescription() {
+        List<TransportEndpointDescription> descs = new ArrayList<>();
+        descs.add(buildTransportEndpointDescription(data().deviceId(), PORT_PRIMARY));
+        descs.add(buildTransportEndpointDescription(data().deviceId(), PORT_SECONDARY));
+        return ProtectedTransportEndpointDescription.of(descs, getPeerId(), FINGERPRINT);
+    }
+
+    private TransportEndpointDescription buildTransportEndpointDescription(DeviceId id, PortNumber port) {
+        return TransportEndpointDescription.builder()
+                .withOutput(new FilteredConnectPoint(new ConnectPoint(id, port)))
+                .build();
+    }
+
+    private TransportEndpointState buildTransportEndpointState(
+            DeviceId id, PortNumber port, PortNumber activePort) {
+        String status = port.equals(activePort) ? STATUS_IN_SERVICE : STATUS_OUT_SERVICE;
+        Map<String, String> attributes = new HashMap<>();
+        attributes.put(INPUT_PORT_STATUS, status);
+        return TransportEndpointState.builder()
+                .withId(TransportEndpointId.of(port.name()))
+                .withDescription(buildTransportEndpointDescription(id, port))
+                .addAttributes(attributes)
+                .build();
+    }
+
+    private int getActiveIndex(List<TransportEndpointState> pathStates, PortNumber activePort) {
+        int activeIndex = 0;
+        for (TransportEndpointState state : pathStates) {
+            if (activePort.equals(state.description().output().connectPoint().port())) {
+                return activeIndex;
+            }
+            ++activeIndex;
+        }
+        return ProtectedTransportEndpointState.ACTIVE_UNKNOWN;
+    }
+
+    private PortNumber acquireActivePort() {
+        String filter = new StringBuilder(xmlOpen(KEY_OPENOPTICALDEV_XMLNS))
+                .append(xmlOpen(KEY_STATE))
+                .append(xmlEmpty(KEY_OPSSTATE))
+                .append(xmlClose(KEY_STATE))
+                .append(xmlClose(KEY_OPENOPTICALDEV))
+                .toString();
+        String reply = netconfGet(handler(), filter);
+        log.debug("Service state replying, {}", reply);
+        return reply.contains(KEY_NAME_PRIMARY) ? PORT_PRIMARY : PORT_SECONDARY;
+    }
+
+    private String formatOperation(PortNumber port, String operation) {
+        String key = port.name().contains(KEY_NAME_PRIMARY) ? KEY_NAME_PRIMARY : KEY_NAME_SECONDARY;
+        return String.format(FMT_OPT, operation, key);
+    }
+
+    private boolean switchDevice(String operation) {
+        log.debug("Switch to {} for Device {}", operation, data().deviceId());
+        String cfg = new StringBuilder(xmlOpen(KEY_OPENOPTICALDEV_XMLNS))
+                .append(xmlOpen(KEY_CONFIG))
+                .append(xml(KEY_OPSCONFIG, operation))
+                .append(xmlClose(KEY_CONFIG))
+                .append(xmlClose(KEY_OPENOPTICALDEV))
+                .toString();
+        return netconfEditConfig(handler(), CFG_MODE_MERGE, cfg);
+    }
+
+    private void addLink(DeviceId peerId) {
+        if (peerId == null) {
+            log.warn("PeerID is null for device {}", data().deviceId());
+            return;
+        }
+        LinkKey link = linkKey(new ConnectPoint(peerId, PORT_VIRTUAL),
+                               new ConnectPoint(data().deviceId(), PORT_VIRTUAL));
+        handler().get(NetworkConfigService.class).addConfig(link, BasicLinkConfig.class)
+                .type(Link.Type.VIRTUAL)
+                .apply();
+    }
+
+    private void removeLink(DeviceId peerId) {
+        if (peerId == null) {
+            log.warn("PeerID is null for device {}", data().deviceId());
+            return;
+        }
+        LinkKey link = linkKey(new ConnectPoint(peerId, PORT_VIRTUAL),
+                               new ConnectPoint(data().deviceId(), PORT_VIRTUAL));
+        handler().get(NetworkConfigService.class).removeConfig(link, BasicLinkConfig.class);
+    }
+
+    private DeviceId getPeerId() {
+        ConnectPoint dstCp = new ConnectPoint(data().deviceId(), PORT_VIRTUAL);
+        Set<Link> links = handler().get(LinkService.class).getIngressLinks(dstCp);
+        for (Link l : links) {
+            if (l.type() == Link.Type.VIRTUAL) {
+                // This devide is the destination and peer is the source.
+                return l.src().deviceId();
+            }
+        }
+        // None of link found, return itself.
+        return data().deviceId();
+    }
+}
diff --git a/drivers/oplink/src/main/resources/oplink-drivers.xml b/drivers/oplink/src/main/resources/oplink-drivers.xml
index 5baf905..faa1a8a 100755
--- a/drivers/oplink/src/main/resources/oplink-drivers.xml
+++ b/drivers/oplink/src/main/resources/oplink-drivers.xml
@@ -26,5 +26,7 @@
                    impl="org.onosproject.drivers.oplink.OplinkOpticalFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.net.behaviour.LambdaQuery"
                    impl="org.onosproject.drivers.oplink.OplinkOpticalLambdaQuery"/>
+        <behaviour api="org.onosproject.net.behaviour.protection.ProtectionConfigBehaviour"
+                   impl="org.onosproject.drivers.oplink.OplinkOpticalProtectionSwitchConfig"/>
     </driver>
 </drivers>