/* * 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.core.CoreService; 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 int FLOWRULE_PRIORITY = 88; private static final String PRIMARY_ID = "primary_port"; private static final String SECONDARY_ID = "secondary_port"; private static final String OPLINK_FINGERPRINT = "OplinkOPS"; private static final String APP_ID = "org.onosproject.drivers.optical"; protected final Logger log = LoggerFactory.getLogger(getClass()); @Override public CompletableFuture<ConnectPoint> createProtectionEndpoint( ProtectedTransportEndpointDescription configuration) { //This OPS device only support one protection group of port 2 and port 3 CompletableFuture result = new CompletableFuture<ConnectPoint>(); //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(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT))); return result; } @Override public CompletableFuture<ConnectPoint> updateProtectionEndpoint( ConnectPoint identifier, ProtectedTransportEndpointDescription configuration) { log.warn("Update protection configuration is not supported by this device"); CompletableFuture result = new CompletableFuture<ConnectPoint>(); result.complete(new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT))); return result; } @Override public CompletableFuture<Boolean> deleteProtectionEndpoint(ConnectPoint identifier) { //OPS has only one protection group CompletableFuture result = new CompletableFuture<Boolean>(); 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<Map<ConnectPoint, ProtectedTransportEndpointDescription>> getProtectionEndpointConfigs() { ConnectPoint cp = new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); Map<ConnectPoint, ProtectedTransportEndpointDescription> protectedGroups = new HashMap<>(); CompletableFuture result = new CompletableFuture<Map<ConnectPoint, ProtectedTransportEndpointDescription>>(); protectedGroups.put(cp, getProtectedTransportEndpointDescription()); result.complete(protectedGroups); return result; } @Override public CompletableFuture<Map<ConnectPoint, ProtectedTransportEndpointState>> getProtectionEndpointStates() { ConnectPoint cp = new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); Map<ConnectPoint, ProtectedTransportEndpointState> protectedGroups = new HashMap<>(); CompletableFuture result = new CompletableFuture<Map<ConnectPoint, ProtectedTransportEndpointState>>(); 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<Void> switchWorkingPath(ConnectPoint identifier, int index) { CompletableFuture result = new CompletableFuture<Boolean>(); //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<TransportEndpointDescription> 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<String, String> getProtectionStateAttributes(PortNumber portNumber) { Map<String, String> attributes = new HashMap<>(); //get status form port annotations, the status is update by hand shaker driver periodically Port port = handler().get(DeviceService.class).getPort(data().deviceId(), portNumber); if (port != null) { String portStatus = port.annotations().value(OpticalAnnotations.INPUT_PORT_STATUS); attributes.put(OpticalAnnotations.INPUT_PORT_STATUS, portStatus); } return attributes; } /* * get activer port number */ private int getActivePort() { Port port = handler().get(DeviceService.class) .getPort(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 active path index */ private int getActiveIndex(List<TransportEndpointState> pathStates) { long activePort = (long) getActivePort(); int activeIndex = 0; for (TransportEndpointState state : pathStates) { if (state.description().output().connectPoint().port().toLong() == activePort) { return activeIndex; } ++activeIndex; } return ProtectedTransportEndpointState.ACTIVE_UNKNOWN; } /* * get protected endpoint state */ private ProtectedTransportEndpointState getProtectedTransportEndpointState() { List<TransportEndpointState> tess = new ArrayList<>(); PortNumber portPrimary = PortNumber.portNumber(PRIMARY_PORT); PortNumber portSecondary = PortNumber.portNumber(SECONDARY_PORT); FilteredConnectPoint fcpPrimary = new FilteredConnectPoint( new ConnectPoint(data().deviceId(), portPrimary)); FilteredConnectPoint fcpSecondary = new FilteredConnectPoint( new ConnectPoint(data().deviceId(), portSecondary)); 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(portPrimary)) .build(); TransportEndpointState tesSecondary = TransportEndpointState.builder() .withDescription(tedSecondary) .withId(TransportEndpointId.of(SECONDARY_ID)) .addAttributes(getProtectionStateAttributes((portSecondary))) .build(); tess.add(tesPrimary); tess.add(tesSecondary); return ProtectedTransportEndpointState.builder() .withDescription(getProtectedTransportEndpointDescription()) .withPathStates(tess) .withActivePathIndex(getActiveIndex(tess)) .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) { // set working port as flow's input port TrafficSelector selector = DefaultTrafficSelector.builder() .matchInPort(workingPort) .build(); // the flow's output port is always the clinet port TrafficTreatment treatment = DefaultTrafficTreatment.builder() .setOutput(PortNumber.portNumber(CLIENT_PORT)) .build(); FlowRule flowRule = DefaultFlowRule.builder() .forDevice(data().deviceId()) .fromApp(handler().get(CoreService.class).getAppId(APP_ID)) .withPriority(FLOWRULE_PRIORITY) .withSelector(selector) .withTreatment(treatment) .makePermanent() .build(); // install flow rule handler().get(FlowRuleService.class).applyFlowRules(flowRule); } /* Delete all the flows to put device in default mode. */ private void deleteFlow() { // remove all the flows. handler().get(FlowRuleService.class).purgeFlowRules(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 = handler().get(NetworkConfigService.class) .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); handler().get(NetworkConfigService.class).removeConfig(link, BasicLinkConfig.class); } private DeviceId getPeerId() { ConnectPoint dstCp = new ConnectPoint(data().deviceId(), PortNumber.portNumber(VIRTUAL_PORT)); 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(); } } return data().deviceId(); } }