| /* |
| * Copyright 2016 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.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 device is the destination and peer is the source. |
| return l.src().deviceId(); |
| } |
| } |
| // None of link found, return itself. |
| return data().deviceId(); |
| } |
| } |