| /* |
| * Copyright 2015-present 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.segmentrouting.pwaas; |
| |
| import org.onlab.osgi.ServiceNotFoundException; |
| import org.onlab.packet.MplsLabel; |
| import org.onlab.packet.VlanId; |
| import org.onosproject.cli.AbstractShellCommand; |
| import org.onosproject.net.ConnectPoint; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.intf.InterfaceService; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| |
| /** |
| * Utility class with static methods that help |
| * parse pseudowire related information and also |
| * verify that a pseudowire combination is valid. |
| */ |
| public final class PwaasUtil { |
| |
| private static final Logger log = LoggerFactory.getLogger(PwaasUtil.class); |
| |
| private static DeviceService deviceService; |
| private static InterfaceService intfService; |
| |
| // Suppress ExceptionInInitializerError and simply set the value to null, |
| // such that unit tests have a chance to replace the variable with mocked service |
| static { |
| try { |
| deviceService = AbstractShellCommand.get(DeviceService.class); |
| } catch (NullPointerException | ServiceNotFoundException e) { |
| deviceService = null; |
| } |
| try { |
| intfService = AbstractShellCommand.get(InterfaceService.class); |
| } catch (NullPointerException | ServiceNotFoundException e) { |
| intfService = null; |
| } |
| } |
| |
| static final String ERR_SERVICE_UNAVAIL = "Service %s not available"; |
| static final String ERR_SAME_DEV = |
| "Pseudowire connection points can not reside in the same node, in pseudowire %d."; |
| static final String ERR_EMPTY_INNER_WHEN_OUTER_PRESENT = |
| "Inner tag should not be empty when outer tag is set for pseudowire %d for %s."; |
| static final String ERR_WILDCARD_VLAN = |
| "Wildcard VLAN matching not yet supported for pseudowire %d."; |
| static final String ERR_DOUBLE_TO_UNTAGGED = |
| "Support for double tag <-> untag is not supported for pseudowire %d."; |
| static final String ERR_DOUBLE_TO_SINGLE = |
| "Support for double-tag<-> single-tag is not supported for pseudowire %d."; |
| static final String ERR_SINGLE_TO_UNTAGGED = |
| "single-tag <-> untag is not supported for pseudowire %d."; |
| static final String ERR_VLAN_TRANSLATION = |
| "We do not support VLAN translation for pseudowire %d."; |
| static final String ERR_DEV_NOT_FOUND = |
| "Device %s does not exist for pseudowire %d."; |
| static final String ERR_PORT_NOT_FOUND = |
| "Port %s of device %s does not exist for pseudowire %d."; |
| |
| private PwaasUtil() { |
| |
| } |
| |
| /** |
| * Parses a vlan as a string. Returns the VlanId if |
| * provided String can be parsed as an integer or is '' / '*' |
| * |
| * @param vlan string as read from configuration |
| * @return VlanId null if error |
| */ |
| public static VlanId parseVlan(String vlan) { |
| |
| if (vlan.equals("*") || vlan.equals("Any")) { |
| return VlanId.vlanId("Any"); |
| } else if (vlan.equals("") || vlan.equals("None")) { |
| return VlanId.vlanId("None"); |
| } else { |
| return VlanId.vlanId(vlan); |
| } |
| } |
| |
| /** |
| * |
| * @param mode RAW or TAGGED |
| * @return the L2Mode if input is correct |
| */ |
| public static L2Mode parseMode(String mode) { |
| checkArgument(mode.equals("RAW") || mode.equals("TAGGED"), |
| "Invalid pseudowire mode of operation, should be TAGGED or RAW."); |
| return L2Mode.valueOf(mode); |
| } |
| |
| /** |
| * |
| * @param label the mpls label of the pseudowire |
| * @return the MplsLabel |
| * @throws IllegalArgumentException if label is invalid |
| */ |
| public static MplsLabel parsePWLabel(String label) { |
| return MplsLabel.mplsLabel(label); |
| } |
| |
| /** |
| * Parses a string as a pseudowire id - which is an integer. |
| * |
| * @param id The id of pw in string form |
| * @return The id of pw as an Integer or null if it failed the conversion. |
| */ |
| public static Integer parsePwId(String id) { |
| return Integer.parseInt(id); |
| } |
| |
| /** |
| * Helper method to verify if the tunnel is whether or not |
| * supported. |
| * |
| * @param l2Tunnel the tunnel to verify |
| */ |
| private static void verifyTunnel(L2Tunnel l2Tunnel) { |
| |
| // Service delimiting tag not supported yet. |
| if (!l2Tunnel.sdTag().equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.format("Service delimiting tag not supported yet for " + |
| "pseudowire %d.", l2Tunnel.tunnelId())); |
| } |
| |
| // Tag mode not supported yet. |
| if (l2Tunnel.pwMode() == L2Mode.TAGGED) { |
| throw new IllegalArgumentException(String.format("Tagged mode not supported yet for pseudowire %d.", |
| l2Tunnel.tunnelId())); |
| } |
| |
| // Raw mode without service delimiting tag |
| // is the only mode supported for now. |
| } |
| |
| /** |
| * Helper method to verify if the policy is whether or not |
| * supported and if policy will be successfully instantiated in the |
| * network. |
| * |
| * @param cP1 pseudo wire endpoint 1 |
| * @param cP2 pseudo wire endpoint 2 |
| * @param ingressInner the ingress inner tag |
| * @param ingressOuter the ingress outer tag |
| * @param egressInner the egress inner tag |
| * @param egressOuter the egress outer tag |
| * @param tunnelId tunnel ID |
| */ |
| static void verifyPolicy(ConnectPoint cP1, |
| ConnectPoint cP2, |
| VlanId ingressInner, |
| VlanId ingressOuter, |
| VlanId egressInner, |
| VlanId egressOuter, |
| Long tunnelId) { |
| |
| if (cP1.deviceId().equals(cP2.deviceId())) { |
| throw new IllegalArgumentException(String.format(ERR_SAME_DEV, tunnelId)); |
| } |
| |
| // We can have multiple tags, all of them can be NONE, |
| // indicating untagged traffic, however, the outer tag can |
| // not have value if the inner tag is None |
| if (ingressInner.equals(VlanId.NONE) && !ingressOuter.equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.format(ERR_EMPTY_INNER_WHEN_OUTER_PRESENT, |
| tunnelId, "cp1")); |
| } |
| |
| if (egressInner.equals(VlanId.NONE) && !egressOuter.equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.format(ERR_EMPTY_INNER_WHEN_OUTER_PRESENT, |
| tunnelId, "cp2")); |
| } |
| |
| if (ingressInner.equals(VlanId.ANY) || |
| ingressOuter.equals(VlanId.ANY) || |
| egressInner.equals(VlanId.ANY) || |
| egressOuter.equals(VlanId.ANY)) { |
| throw new IllegalArgumentException(String.format(ERR_WILDCARD_VLAN, tunnelId)); |
| } |
| |
| if (((!ingressOuter.equals(VlanId.NONE) && !ingressInner.equals(VlanId.NONE)) && |
| (egressOuter.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE))) |
| || ((ingressOuter.equals(VlanId.NONE) && ingressInner.equals(VlanId.NONE)) && |
| (!egressOuter.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE)))) { |
| throw new IllegalArgumentException(String.format(ERR_DOUBLE_TO_UNTAGGED, tunnelId)); |
| } |
| if ((!ingressInner.equals(VlanId.NONE) && |
| ingressOuter.equals(VlanId.NONE) && |
| !egressOuter.equals(VlanId.NONE)) |
| || (egressOuter.equals(VlanId.NONE) && |
| !egressInner.equals(VlanId.NONE) && |
| !ingressOuter.equals(VlanId.NONE))) { |
| throw new IllegalArgumentException(String.format(ERR_DOUBLE_TO_SINGLE, tunnelId)); |
| } |
| |
| if ((ingressInner.equals(VlanId.NONE) && !egressInner.equals(VlanId.NONE)) |
| || (!ingressInner.equals(VlanId.NONE) && egressInner.equals(VlanId.NONE))) { |
| throw new IllegalArgumentException(String.format(ERR_SINGLE_TO_UNTAGGED, tunnelId)); |
| } |
| |
| // FIXME PW VLAN translation is not supported on Dune |
| // Need to explore doing that in egress table later if there is a requirement |
| if (!ingressInner.equals(egressInner) || !ingressOuter.equals(egressOuter)) { |
| throw new IllegalArgumentException(String.format(ERR_VLAN_TRANSLATION, tunnelId)); |
| } |
| |
| if (deviceService == null) { |
| throw new IllegalStateException(String.format(ERR_SERVICE_UNAVAIL, "DeviceService")); |
| } |
| |
| // check if cp1 and port of cp1 exist |
| if (deviceService.getDevice(cP1.deviceId()) == null) { |
| throw new IllegalArgumentException(String.format(ERR_DEV_NOT_FOUND, cP1.deviceId(), tunnelId)); |
| } |
| |
| if (deviceService.getPort(cP1) == null) { |
| throw new IllegalArgumentException(String.format(ERR_PORT_NOT_FOUND, cP1.port(), |
| cP1.deviceId(), tunnelId)); |
| } |
| |
| // check if cp2 and port of cp2 exist |
| if (deviceService.getDevice(cP2.deviceId()) == null) { |
| throw new IllegalArgumentException(String.format(ERR_DEV_NOT_FOUND, cP2.deviceId(), tunnelId)); |
| } |
| |
| if (deviceService.getPort(cP2) == null) { |
| throw new IllegalArgumentException(String.format(ERR_PORT_NOT_FOUND, cP2.port(), |
| cP2.deviceId(), tunnelId)); |
| } |
| } |
| |
| /** |
| * Verifies that the pseudowires will not conflict with each other. |
| * |
| * Further, check if vlans for connect points are already used. |
| * |
| * @param tunnel Tunnel for pw |
| * @param policy Policy for pw |
| * @param labelSet Label set used so far with this configuration |
| * @param vlanSet Vlan set used with this configuration |
| * @param tunnelSet Tunnel set used with this configuration |
| */ |
| private static void verifyGlobalValidity(L2Tunnel tunnel, |
| L2TunnelPolicy policy, |
| Set<MplsLabel> labelSet, |
| Map<ConnectPoint, Set<VlanId>> vlanSet, |
| Set<Long> tunnelSet) { |
| |
| if (tunnelSet.contains(tunnel.tunnelId())) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Tunnel Id %d already used by" + |
| " another pseudowire, in " + |
| "pseudowire %d!", |
| tunnel.tunnelId(), |
| tunnel.tunnelId()))); |
| } |
| tunnelSet.add(tunnel.tunnelId()); |
| |
| // check if tunnel id is used again |
| ConnectPoint cP1 = policy.cP1(); |
| ConnectPoint cP2 = policy.cP2(); |
| |
| // insert cps to hashmap if this is the first time seen |
| if (!vlanSet.containsKey(cP1)) { |
| vlanSet.put(cP1, new HashSet<VlanId>()); |
| } |
| if (!vlanSet.containsKey(cP2)) { |
| vlanSet.put(cP2, new HashSet<VlanId>()); |
| } |
| |
| // if single tagged or untagged vlan is the inner |
| // if double tagged vlan is the outer |
| VlanId vlanToCheckCP1; |
| if (policy.cP1OuterTag().equals(VlanId.NONE)) { |
| vlanToCheckCP1 = policy.cP1InnerTag(); |
| } else { |
| vlanToCheckCP1 = policy.cP1OuterTag(); |
| } |
| |
| VlanId vlanToCheckCP2; |
| if (policy.cP2OuterTag().equals(VlanId.NONE)) { |
| vlanToCheckCP2 = policy.cP2InnerTag(); |
| } else { |
| vlanToCheckCP2 = policy.cP2OuterTag(); |
| } |
| |
| if (labelSet.contains(tunnel.pwLabel())) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Label %s already used by another" + |
| " pseudowire, in pseudowire %d!", |
| tunnel.pwLabel(), tunnel.tunnelId()))); |
| } |
| labelSet.add(tunnel.pwLabel()); |
| |
| if (vlanSet.get(cP1).contains(vlanToCheckCP1)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already used " + |
| "by another pseudowire, in " + |
| "pseudowire" + |
| " %d!", vlanToCheckCP1, cP1, |
| tunnel.tunnelId()))); |
| } |
| vlanSet.get(cP1).add(vlanToCheckCP1); |
| |
| if (vlanSet.get(cP2).contains(vlanToCheckCP2)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s already used" + |
| " by another pseudowire, in" + |
| " pseudowire %d!", vlanToCheckCP2, |
| cP2, |
| tunnel.tunnelId()))); |
| } |
| vlanSet.get(cP2).add(vlanToCheckCP2); |
| |
| if (intfService == null) { |
| throw new IllegalStateException(String.format(ERR_SERVICE_UNAVAIL, "InterfaceService")); |
| } |
| |
| // check that vlans for the connect points are not used |
| intfService.getInterfacesByPort(cP1).stream() |
| .forEach(intf -> { |
| |
| // check if tagged pw affects tagged interface |
| if (intf.vlanTagged().contains(vlanToCheckCP1)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP1 %s already" + |
| " used for this" + |
| " interface, in" + |
| " pseudowire %d!", |
| vlanToCheckCP1, cP1, |
| tunnel.tunnelId()))); |
| } |
| |
| // if vlanNative != null this interface is configured with untagged traffic also |
| // check if it collides with untagged interface |
| if ((intf.vlanNative() != null) && vlanToCheckCP1.equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP1 " + |
| "%s already used " + |
| "for this " + |
| "interface, in " + |
| "pseudowire " + |
| "%d!", cP1, |
| tunnel.tunnelId()))); |
| } |
| |
| // if vlanUntagged != null this interface is configured only with untagged traffic |
| // check if it collides with untagged interface |
| if ((intf.vlanUntagged() != null) && vlanToCheckCP1.equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for " + |
| "cP1 %s already" + |
| " used for this" + |
| " interface," + |
| " in pseudowire %d!", |
| cP1, tunnel.tunnelId()))); |
| } |
| }); |
| |
| intfService.getInterfacesByPort(cP2).stream() |
| .forEach(intf -> { |
| if (intf.vlanTagged().contains(vlanToCheckCP2)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Vlan '%s' for cP2 %s " + |
| " used for " + |
| "this interface, " + |
| "in pseudowire %d!", |
| vlanToCheckCP2, cP2, |
| tunnel.tunnelId()))); |
| } |
| |
| // if vlanNative != null this interface is configured with untagged traffic also |
| // check if it collides with untagged interface |
| if ((intf.vlanNative() != null) && vlanToCheckCP2.equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic " + |
| "for cP2 %s " + |
| "already " + |
| "used for this" + |
| " interface, " + |
| "in pseudowire %d!", |
| cP2, tunnel.tunnelId()))); |
| } |
| |
| // if vlanUntagged != null this interface is configured only with untagged traffic |
| // check if it collides with untagged interface |
| if ((intf.vlanUntagged() != null) && vlanToCheckCP2.equals(VlanId.NONE)) { |
| throw new IllegalArgumentException(String.valueOf(String.format("Untagged traffic for cP2 %s" + |
| " already" + |
| " used for " + |
| "this interface, " + |
| "in pseudowire %d!", |
| cP2, tunnel.tunnelId()))); |
| } |
| }); |
| |
| } |
| |
| /** |
| * Helper method to verify the integrity of the pseudo wire. |
| * |
| * @param l2TunnelDescription the pseudo wire description |
| */ |
| private static void verifyPseudoWire(L2TunnelDescription l2TunnelDescription, |
| Set<MplsLabel> labelSet, |
| Map<ConnectPoint, Set<VlanId>> vlanset, |
| Set<Long> tunnelSet) { |
| |
| L2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel(); |
| L2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy(); |
| |
| verifyTunnel(l2Tunnel); |
| |
| verifyPolicy( |
| l2TunnelPolicy.cP1(), |
| l2TunnelPolicy.cP2(), |
| l2TunnelPolicy.cP1InnerTag(), |
| l2TunnelPolicy.cP1OuterTag(), |
| l2TunnelPolicy.cP2InnerTag(), |
| l2TunnelPolicy.cP2OuterTag(), |
| l2Tunnel.tunnelId() |
| ); |
| |
| verifyGlobalValidity(l2Tunnel, |
| l2TunnelPolicy, |
| labelSet, |
| vlanset, |
| tunnelSet); |
| |
| } |
| |
| public static L2TunnelHandler.Result configurationValidity(List<L2TunnelDescription> pseudowires) { |
| |
| // structures to keep pw information |
| // in order to see if instantiating them will create |
| // problems |
| Set<Long> tunIds = new HashSet<>(); |
| Set<MplsLabel> labelsUsed = new HashSet<>(); |
| Map<ConnectPoint, Set<VlanId>> vlanIds = new HashMap<>(); |
| |
| // TODO : I know we should not use exceptions for flow control, |
| // however this code was originally implemented in the configuration |
| // addition where the exceptions were propagated and the configuration was |
| // deemed not valid. I plan in the future to refactor the parts that |
| // check the pseudowire validity. |
| // |
| // Ideally we would like to return a String which could also return to |
| // the user issuing the rest request for adding the pseudowire. |
| try { |
| // check that pseudowires can be instantiated in the network |
| // we try to guarantee that all the pws will work before |
| // instantiating any of them |
| for (L2TunnelDescription pw : pseudowires) { |
| log.debug("Verifying pseudowire {}", pw); |
| verifyPseudoWire(pw, labelsUsed, vlanIds, tunIds); |
| } |
| |
| return L2TunnelHandler.Result.SUCCESS; |
| } catch (Exception e) { |
| log.error("Caught exception while validating pseudowire : {}", e.getMessage()); |
| return L2TunnelHandler.Result.CONFIGURATION_ERROR |
| .appendError(e.getMessage()); |
| } |
| } |
| } |