blob: 7cb60a64170fe02481fa23917fbe095c76d4147c [file] [log] [blame]
/*
* 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());
}
}
}