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>