From 1085239c32d46c2b7adb47e02ac0d341b6931d2c Mon Sep 17 00:00:00 2001 From: Jimmy Jin Date: Tue, 24 Jan 2017 13:45:13 -0800 Subject: [PATCH] Oplink Protection Switch Driver for Protection Behavior Change-Id: Ibf7e48aa158c8b007474b3fc89f8be092776a2ba --- .../net/optical/OpticalAnnotations.java | 30 +- .../handshaker/OplinkSwitchHandshaker.java | 33 +- .../protection/OplinkSwitchProtection.java | 333 ++++++++++++++++++ .../optical/protection/package-info.java | 20 ++ .../src/main/resources/optical-drivers.xml | 6 +- 5 files changed, 404 insertions(+), 18 deletions(-) create mode 100644 drivers/optical/src/main/java/org/onosproject/driver/optical/protection/OplinkSwitchProtection.java create mode 100644 drivers/optical/src/main/java/org/onosproject/driver/optical/protection/package-info.java diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/OpticalAnnotations.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/OpticalAnnotations.java index b53b3d2772..97cd7a7633 100644 --- a/apps/optical-model/src/main/java/org/onosproject/net/optical/OpticalAnnotations.java +++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/OpticalAnnotations.java @@ -50,11 +50,17 @@ public final class OpticalAnnotations { public static final String TARGET_POWER = "targetPower"; /** - * Annotation key for optical port's current power. + * Annotation key for optical port's current receiving power. * Value is expected to be an integer. */ public static final String CURRENT_POWER = "currentPower"; + /** + * Annotation key for bidirectional optical port's transmitting power. + * Value is expected to be an integer. + */ + public static final String OUTPUT_POWER = "ouputPower"; + /** * Annotation key for optical port's neighbor's DeviceId#toString(). */ @@ -65,4 +71,26 @@ public final class OpticalAnnotations { * Value is expected to be an integer. */ public static final String NEIGHBOR_PORT = "neighborPort"; + + /** + * Annotation key for optical port's status in receiving direction. + * Value is expected to be STATUS_IN_SERVICE or STATUS_OUT_SERVICE. + */ + public static final String INPUT_PORT_STATUS = "inputStatus"; + + /** + * Annotation key for optical port's status in transmitting direction. + * Value is expected to be STATUS_IN_SERVICE or STATUS_OUT_SERVICE. + */ + public static final String OUTPUT_PORT_STATUS = "ouputStatus"; + + /** + * Annotation value for optical port's in-service status. + */ + public static final String STATUS_IN_SERVICE = "inService"; + + /** + * Annotation value for optical port's out-of-service status. + */ + public static final String STATUS_OUT_SERVICE = "outOfService"; } diff --git a/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkSwitchHandshaker.java b/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkSwitchHandshaker.java index 194bea9341..84f1e37683 100644 --- a/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkSwitchHandshaker.java +++ b/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkSwitchHandshaker.java @@ -15,6 +15,7 @@ */ package org.onosproject.driver.optical.handshaker; +import org.onosproject.net.behaviour.protection.ProtectionConfigBehaviour; import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry; import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsReply; import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsRequest; @@ -54,6 +55,8 @@ import org.projectfloodlight.openflow.protocol.OFObject; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import static org.onosproject.net.optical.OpticalAnnotations.*; + /** * Oplink Open Flow Protection Optical Switch handshaker - for Open Flow 1.3. @@ -61,14 +64,11 @@ import com.google.common.collect.ImmutableSet; */ public class OplinkSwitchHandshaker extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch { + private static final String PROTECTION_FINGERPRINT = "OplinkOPS"; + private static final int PROTECTION_VIRTUAL_PORT = 0; + private final AtomicBoolean driverHandshakeComplete = new AtomicBoolean(false); private List opticalPorts = new ArrayList<>(); - private static final String INPUT_PORT_STATUS = "inputStatus"; - private static final String OUTPUT_PORT_STATUS = "ouputStatus"; - private static final String INPUT_PORT_POWER = "inputPower"; - private static final String OUTPUT_PORT_POWER = "ouputPower"; - private static final String IN_SERVICE_ANNOTATION = "inService"; - private static final String OUT_SERVICE_ANNOTATION = "outOfService"; private enum SubType { PORT_DESC_STATS, // Port description stats openflow message @@ -154,7 +154,8 @@ public class OplinkSwitchHandshaker extends AbstractOpenFlowSwitch implements Op private void sendHandshakeOFExperimenterPortDescRequest() throws IOException { - /**Note: + /* + *Note: * Oplink protection switch and Calient switch are both optical fiber switch, * so Calient port description matches well for Oplink switch. * OFCalientPortDescStatsRequest is generated by loxi. @@ -224,23 +225,29 @@ public class OplinkSwitchHandshaker extends AbstractOpenFlowSwitch implements Op for (Port port : ports) { DefaultAnnotations.Builder builder = DefaultAnnotations.builder(); builder.putAll(port.annotations()); + + //set fingerprint for the virtual port (port 0) + if (port.number().toLong() == PROTECTION_VIRTUAL_PORT) { + builder.set(ProtectionConfigBehaviour.FINGERPRINT, PROTECTION_FINGERPRINT); + } + OFCalientPortStatsEntry entry = statsMap.get(port.number().toLong()); if (entry == null) { continue; } - builder.set(INPUT_PORT_POWER, entry.getInportPower()); - builder.set(OUTPUT_PORT_POWER, entry.getOutportPower()); + builder.set(CURRENT_POWER, entry.getInportPower()); + builder.set(OUTPUT_POWER, entry.getOutportPower()); //Note: There are some mistakes about bitmask encoding and decoding in openflowj. //We just use this code for a short term, and will modify in the future. if (entry.getInOperStatus().isEmpty()) { - builder.set(INPUT_PORT_STATUS, IN_SERVICE_ANNOTATION); + builder.set(INPUT_PORT_STATUS, STATUS_IN_SERVICE); } else { - builder.set(INPUT_PORT_STATUS, OUT_SERVICE_ANNOTATION); + builder.set(INPUT_PORT_STATUS, STATUS_OUT_SERVICE); } if (entry.getOutOperStatus().isEmpty()) { - builder.set(OUTPUT_PORT_STATUS, IN_SERVICE_ANNOTATION); + builder.set(OUTPUT_PORT_STATUS, STATUS_IN_SERVICE); } else { - builder.set(OUTPUT_PORT_STATUS, OUT_SERVICE_ANNOTATION); + builder.set(OUTPUT_PORT_STATUS, STATUS_OUT_SERVICE); } portDescs.add(new DefaultPortDescription(port.number(), port.isEnabled(), port.type(), port.portSpeed(), builder.build())); diff --git a/drivers/optical/src/main/java/org/onosproject/driver/optical/protection/OplinkSwitchProtection.java b/drivers/optical/src/main/java/org/onosproject/driver/optical/protection/OplinkSwitchProtection.java new file mode 100644 index 0000000000..f512b9c467 --- /dev/null +++ b/drivers/optical/src/main/java/org/onosproject/driver/optical/protection/OplinkSwitchProtection.java @@ -0,0 +1,333 @@ +/* + * Copyright 2017-present 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.driver.optical.protection; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.FilteredConnectPoint; +import org.onosproject.net.Link; +import org.onosproject.net.LinkKey; +import org.onosproject.net.Port; +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.device.DeviceService; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.optical.OpticalAnnotations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.net.LinkKey.linkKey; + +/** + * Implementations of the protection behaviours for Oplink Optical Protection Switch (OPS). + * - Oplink OPS has 3 logical bi-directional ports (6 uni-directional physical ports): + * - port 1 is primary port for network side + * - port 2 is secondary port for network side. + * - port 3 is connected to the client side port; + * - 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 OplinkSwitchProtection extends AbstractHandlerBehaviour implements ProtectionConfigBehaviour { + + private static final int VIRTUAL_PORT = 0; + private static final int PRIMARY_PORT = 1; + private static final int SECONDARY_PORT = 2; + private static final int CLIENT_PORT = 3; + private static final String PRIMARY_ID = "primary_port"; + private static final String SECONDARY_ID = "secondary_port"; + private static final String OPLINK_FINGERPRINT = "OplinkOPS"; + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private FlowRuleService flowRuleService = this.handler().get(FlowRuleService.class); + private DeviceService deviceService = this.handler().get(DeviceService.class); + private NetworkConfigService netCfgService = this.handler().get(NetworkConfigService.class); + + @Override + public CompletableFuture createProtectionEndpoint( + ProtectedTransportEndpointDescription configuration) { + //This OPS device only support one protection group of port 2 and port 3 + + CompletableFuture result = new CompletableFuture(); + + //add flow from client port to virtual port. This set device in auto switch mode + addFlow(PortNumber.portNumber(VIRTUAL_PORT)); + + //add a virtual link bewteen two virtual ports of this device and peer + addLinkToPeer(configuration.peer()); + + result.complete(new ConnectPoint(this.data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT))); + + return result; + } + + @Override + public CompletableFuture updateProtectionEndpoint( + ConnectPoint identifier, ProtectedTransportEndpointDescription configuration) { + + log.warn("Update protection configuration is not supported by this device"); + + CompletableFuture result = new CompletableFuture(); + result.complete(new ConnectPoint(this.data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT))); + + return result; + } + + @Override + public CompletableFuture deleteProtectionEndpoint(ConnectPoint identifier) { + //OPS has only one protection group + CompletableFuture result = new CompletableFuture(); + + if (identifier.port().toLong() == VIRTUAL_PORT) { + //add a link bewteen two virtual ports of this device and peer + removeLinkToPeer(getPeerId()); + deleteFlow(); + result.complete(true); + } else { + result.complete(false); + } + + return result; + } + + @Override + public CompletableFuture> getProtectionEndpointConfigs() { + ConnectPoint cp = new ConnectPoint(this.data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); + + Map protectedGroups = new HashMap<>(); + CompletableFuture result = new CompletableFuture>(); + + protectedGroups.put(cp, getProtectedTransportEndpointDescription()); + result.complete(protectedGroups); + + return result; + } + + @Override + public CompletableFuture> getProtectionEndpointStates() { + ConnectPoint cp = new ConnectPoint(this.data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); + + Map protectedGroups = new HashMap<>(); + CompletableFuture result = new CompletableFuture>(); + + protectedGroups.put(cp, getProtectedTransportEndpointState()); + result.complete(protectedGroups); + + return result; + } + + /* + ** index: PRIMARY_PORT - manual switch to primary port + * SECONDARY_PORT - manual switch to Secondary port + * VIRTUAL_PORT - automatic switch mode + */ + @Override + public CompletableFuture switchWorkingPath(ConnectPoint identifier, int index) { + CompletableFuture result = new CompletableFuture(); + + //send switch command to switch to device by sending a flow-mod message. + if (identifier.port().toLong() == VIRTUAL_PORT) { + deleteFlow(); + addFlow(PortNumber.portNumber(index)); + result.complete(true); + } else { + result.complete(false); + } + + return result; + } + + /* + * return the protected endpoint description of this devices + */ + private ProtectedTransportEndpointDescription getProtectedTransportEndpointDescription() { + List teds = new ArrayList<>(); + FilteredConnectPoint fcpPrimary = new FilteredConnectPoint( + new ConnectPoint(data().deviceId(), PortNumber.portNumber(PRIMARY_PORT))); + FilteredConnectPoint fcpSecondary = new FilteredConnectPoint( + new ConnectPoint(data().deviceId(), PortNumber.portNumber(SECONDARY_PORT))); + TransportEndpointDescription tedPrimary = TransportEndpointDescription.builder() + .withOutput(fcpPrimary).build(); + TransportEndpointDescription tedSecondary = TransportEndpointDescription.builder() + .withOutput(fcpSecondary).build(); + + teds.add(tedPrimary); + teds.add(tedSecondary); + return ProtectedTransportEndpointDescription.of(teds, + getPeerId(), + OPLINK_FINGERPRINT); + } + + /* + * get endpoint state attributes + */ + private Map getProtectionStateAttributes(PortNumber portNumber) { + Map attributes = new HashMap<>(); + + //get status form port annotations, the status is update by hand shaker driver periodically + Port port = deviceService.getPort(this.data().deviceId(), portNumber); + if (port != null) { + String portStatus = port.annotations().value(OpticalAnnotations.INPUT_PORT_STATUS); + attributes.put(OpticalAnnotations.INPUT_PORT_STATUS, portStatus); + } + return attributes; + } + + private int getActiveIndex() { + Port port = deviceService.getPort(this.data().deviceId(), PortNumber.portNumber(PRIMARY_PORT)); + if (port != null) { + if (port.annotations().value(OpticalAnnotations.INPUT_PORT_STATUS) + .equals(OpticalAnnotations.STATUS_IN_SERVICE)) { + return PRIMARY_PORT; + } + } + return SECONDARY_PORT; + } + + /* + * get protected endpoint state + */ + private ProtectedTransportEndpointState getProtectedTransportEndpointState() { + List tess = new ArrayList<>(); + FilteredConnectPoint fcpPrimary = new FilteredConnectPoint( + new ConnectPoint(data().deviceId(), PortNumber.portNumber(PRIMARY_PORT))); + FilteredConnectPoint fcpSecondary = new FilteredConnectPoint( + new ConnectPoint(data().deviceId(), PortNumber.portNumber(SECONDARY_PORT))); + TransportEndpointDescription tedPrimary = TransportEndpointDescription.builder() + .withOutput(fcpPrimary).build(); + TransportEndpointDescription tedSecondary = TransportEndpointDescription.builder() + .withOutput(fcpSecondary).build(); + + TransportEndpointState tesPrimary = TransportEndpointState.builder() + .withDescription(tedPrimary) + .withId(TransportEndpointId.of(PRIMARY_ID)) + .addAttributes(getProtectionStateAttributes(PortNumber.portNumber(PRIMARY_PORT))) + .build(); + TransportEndpointState tesSecondary = TransportEndpointState.builder() + .withDescription(tedSecondary) + .withId(TransportEndpointId.of(SECONDARY_ID)) + .addAttributes(getProtectionStateAttributes((PortNumber.portNumber(SECONDARY_PORT)))) + .build(); + + tess.add(tesPrimary); + tess.add(tesSecondary); + return ProtectedTransportEndpointState.builder() + .withDescription(getProtectedTransportEndpointDescription()) + .withPathStates(tess) + .withActivePathIndex(getActiveIndex()) + .build(); + } + + /* + * - Protection switch is controlled by setting up flow on the device + * - There is only one flow on the device at any point + * - A flow from virtual port to client port indicates the device is in auto switch mode + * - A flow from primary port to client port indicates the device is manually switched to primary + * - A flow from secondary port to client port indicates the device is manually switched to secondary + */ + private void addFlow(PortNumber workingPort) { + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + FlowRule.Builder flowRule = DefaultFlowRule.builder(); + + //set working port as flow's input port + selectorBuilder.matchInPort(workingPort); + + //the flow's output port is always the clinet port + treatment.setOutput(PortNumber.portNumber(CLIENT_PORT)); + + flowRule.forDevice(this.data().deviceId()) + .withSelector(selectorBuilder.build()) + .withTreatment(treatment.build()) + .makePermanent(); + + // install flow rule + flowRuleService.applyFlowRules(flowRule.build()); + } + /* + Delete all the flows to put device in default mode. + */ + private void deleteFlow() { + // remove all the flows. + flowRuleService.purgeFlowRules(this.data().deviceId()); + } + + private void addLinkToPeer(DeviceId peerId) { + + if (peerId == null) { + log.warn("PeerID is null for device {}", data().deviceId()); + return; + } + ConnectPoint dstCp = new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); + ConnectPoint srcCp = new ConnectPoint(peerId, PortNumber.portNumber(VIRTUAL_PORT)); + LinkKey link = linkKey(srcCp, dstCp); + BasicLinkConfig cfg = netCfgService.addConfig(link, BasicLinkConfig.class); + cfg.type(Link.Type.VIRTUAL); + cfg.apply(); + } + + private void removeLinkToPeer(DeviceId peerId) { + if (peerId == null) { + log.warn("PeerID is null for device {}", data().deviceId()); + return; + } + ConnectPoint dstCp = new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); + ConnectPoint srcCp = new ConnectPoint(peerId, PortNumber.portNumber(VIRTUAL_PORT)); + LinkKey link = linkKey(srcCp, dstCp); + netCfgService.removeConfig(link, BasicLinkConfig.class); + } + + private DeviceId getPeerId() { + ConnectPoint dstCp = new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); + Set links = this.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(); + } + } + + return null; + } +} diff --git a/drivers/optical/src/main/java/org/onosproject/driver/optical/protection/package-info.java b/drivers/optical/src/main/java/org/onosproject/driver/optical/protection/package-info.java new file mode 100644 index 0000000000..2c3d7ac7ca --- /dev/null +++ b/drivers/optical/src/main/java/org/onosproject/driver/optical/protection/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2017-present 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. + */ + +/** + * Implementations of the protection behaviours for optical devices. + */ +package org.onosproject.driver.optical.protection; \ No newline at end of file diff --git a/drivers/optical/src/main/resources/optical-drivers.xml b/drivers/optical/src/main/resources/optical-drivers.xml index 6096b991be..21829a15b3 100644 --- a/drivers/optical/src/main/resources/optical-drivers.xml +++ b/drivers/optical/src/main/resources/optical-drivers.xml @@ -69,10 +69,8 @@ - - - +