blob: 37e7152a65c67d3009f9f692a44d35cd326e7fe5 [file] [log] [blame]
/*
* 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.ecord.carrierethernet.app;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.VlanId;
import org.onosproject.ecord.carrierethernet.api.CarrierEthernetProvisionerService;
import org.onosproject.ecord.carrierethernet.api.CarrierEthernetService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.Link;
import org.onosproject.net.Path;
import org.onosproject.net.Port;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.topology.PathService;
import org.onosproject.net.topology.TopologyService;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Implementation of a Carrier Ethernet Manager.
*/
@Component(immediate = true)
@Service
public class CarrierEthernetManager implements CarrierEthernetService {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkService linkService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PathService pathService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService networkConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CarrierEthernetProvisionerService ceProvisioner;
// Keeps track of the next S-VLAN tag the app will try to use
private static short nextVlanId = 1;
// Keeps track of the next EVC id the app will try to use
// TODO: Use Identifier class instead
private static short nextEvcShortId = 1;
private boolean evcFragmentationEnabled = false;
private boolean prevEvcFragmentationStatus = evcFragmentationEnabled;
// TODO: Implement distributed store for EVCs
// The installed EVCs
private final Map<String, CarrierEthernetVirtualConnection> evcMap = new ConcurrentHashMap<>();
// TODO: Implement distributed store for Forwarding Constructs
// The installed Forwarding Constructs
private final Map<String, CarrierEthernetForwardingConstruct> fcMap = new ConcurrentHashMap<>();
// TODO: Implement distributed store for CE UNIs
// The installed CE UNIs
private final Map<String, CarrierEthernetUni> uniMap = new ConcurrentHashMap<>();
private final Set<String> removedUniSet = Sets.newConcurrentHashSet();
// TODO: Implement distributed store for CE LTPs
// The installed CE LTPs
private final Map<String, CarrierEthernetLogicalTerminationPoint> ltpMap = new ConcurrentHashMap<>();
// The LTP ids that have been explicitly removed (or requested to be removed) from the global LTP map
private final Set<String> removedLtpSet = Sets.newConcurrentHashSet();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry cfgRegistry;
private final List<ConfigFactory<?, ?>> factories = ImmutableList.of(
new ConfigFactory<ConnectPoint, PortVlanConfig>(CONNECT_POINT_SUBJECT_FACTORY,
PortVlanConfig.class, PortVlanConfig.CONFIG_KEY) {
@Override
public PortVlanConfig createConfig() {
return new PortVlanConfig();
}
});
// Map of connect points and corresponding VLAN tag
private final Map<ConnectPoint, VlanId> portVlanMap = new ConcurrentHashMap<>();
private NetworkConfigListener netcfgListener = new InternalNetworkConfigListener();
/**
* Activate this component.
*/
@Activate
public void activate() {
networkConfigService.addListener(netcfgListener);
factories.forEach(cfgRegistry::registerConfigFactory);
}
/**
* Deactivate this component.
*/
@Deactivate
public void deactivate() {
networkConfigService.removeListener(netcfgListener);
factories.forEach(cfgRegistry::unregisterConfigFactory);
removeAllEvcs();
removeAllFcs();
}
@Override
public Map<String, CarrierEthernetVirtualConnection> evcMap() {
return this.evcMap;
}
@Override
public CarrierEthernetVirtualConnection getEvc(String evcId) {
return ((evcMap.get(evcId) == null) ? null : evcMap.get(evcId));
}
@Override
public CarrierEthernetForwardingConstruct getFc(String fcId) {
return ((fcMap.get(fcId) == null) ? null : fcMap.get(fcId));
}
@Override
public Map<String, CarrierEthernetForwardingConstruct> fcMap() {
return fcMap;
}
@Override
public Map<String, CarrierEthernetLogicalTerminationPoint> ltpMap() {
return ltpMap;
}
@Override
public Map<String, CarrierEthernetUni> getUniMap() {
return uniMap;
}
/**
* Verify the validity of an EVC representation taking also into account current network status.
*
* @param originalEvc the provided EVC representation
* @return a valid, potentially modified EVC representation, or null if the EVC could not be validated
*/
private CarrierEthernetVirtualConnection validateEvc(CarrierEthernetVirtualConnection originalEvc) {
// Make a copy of the provided EVC, since it may be modified
CarrierEthernetVirtualConnection evc = originalEvc;
// Try to set a unique numerical id for the EVC unless the EVC is being updated
// FIXME: Check again the EVC update case
evc.setShortId(generateEvcShortId());
if (evc.shortId() == null) {
log.error("No available EVC id found.");
return null;
}
// Generate and set unique FC id
evc.setId(generateEvcId(evc));
// Verify that CE-VLAN ID is provided to either all UNIs or none
// and set the virtualEvc flag accordingly
// Note: Checking also that all NIs are UNIs
boolean isVirtual = false;
Iterator<CarrierEthernetUni> it = evc.uniSet().iterator();
while (it.hasNext()) {
CarrierEthernetUni ni = it.next();
if (ni.ceVlanId() == VlanId.NONE && isVirtual) {
log.error("Could not validate the virtual status of the EVC.");
return null;
} else if (ni.ceVlanId() != VlanId.NONE) {
isVirtual = true;
}
}
evc.setIsVirtual(isVirtual);
// Set unique id for the EVC unless the EVC is being updated
if (evc.id() == null) {
evc.setId(generateEvcId(evc));
}
Set<CarrierEthernetUni> validatedUniSet = new HashSet<>();
// TODO: Refactor according to the validateFc method
// Note: Cannot use the validateFc method here,
// because FCs can also be standalone
// Check the UNIs of the EVC, possibly removing UNIs that are
// incompatible with existing global ones
it = evc.uniSet().iterator();
while (it.hasNext()) {
CarrierEthernetUni uni = it.next();
// Change the name of the UNI's BWP to the EVC name if it is an EVC BWP
if (uni.bwp().type().equals(CarrierEthernetBandwidthProfile.Type.EVC)) {
uni.bwp().setId(evc.id());
}
// Check first if corresponding global UNI already exists
// by checking against the global UNI Map
if (uniMap.keySet().contains(uni.id())) {
CarrierEthernetUni existingUni = uniMap.get(uni.id());
// Check if the EVC-specific UNI is compatible with the global one
if (!(existingUni.validateEcNi(uni))) {
// If EVC is of ROOT_MULTIPOINT type and we have removed the root, return null
if (evc.type() == CarrierEthernetVirtualConnection.Type.ROOT_MULTIPOINT &&
uni.role() == CarrierEthernetUni.Role.ROOT) {
log.error("Root UNI could not be added to %s EVC.", evc.type().name());
return null;
}
log.warn("UNI {} could not be added to EVC.", uni.id());
continue;
} else {
// Add UNI to EVC
validatedUniSet.add(uni);
}
} else {
// Add UNI to EVC
validatedUniSet.add(uni);
}
}
// Update the EVC UNI set, based on the validated UNIs
evc.setUniSet(validatedUniSet);
// TODO: Check that an ROOT_MULTIPOINT EVC has at most one ROOT
if (evc.uniSet().size() > evc.maxNumUni()) {
log.error("{} EVC can have at most {} UNIs.", evc.maxNumUni());
return null;
}
if ((evc.type().equals(CarrierEthernetVirtualConnection.Type.ROOT_MULTIPOINT)
|| evc.type().equals(CarrierEthernetVirtualConnection.Type.MULTIPOINT_TO_MULTIPOINT))
&& (evc.uniSet().size() < 2)) {
log.error("{} EVC requires at least two UNIs.", evc.type().name());
return null;
}
if (evc.type().equals(CarrierEthernetVirtualConnection.Type.POINT_TO_POINT) && (evc.uniSet().size() != 2)) {
log.error("{} EVC requires exactly two UNIs.", evc.type().name());
return null;
}
return evc;
}
@Override
public CarrierEthernetVirtualConnection installEvc(CarrierEthernetVirtualConnection evc) {
// If EVC already exists, remove it and reestablish with new parameters
if (evc.id() != null && evcMap.containsKey(evc.id())) {
return updateEvc(evc);
} else {
// id will be generated during validation below
evc.setId(null);
}
if (validateEvc(evc) == null) {
log.error("EVC could not be installed, please check log for details.");
return null;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// This is the "orchestration" part of the CE app
//////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: Add configurable parameter to determine if fragmentation will take place
if (evcFragmentationEnabled) {
evc.setFcSet(fragmentEvc(evc));
} else {
evc.setFcSet(Collections.singleton(fcFromEvc(evc)));
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Assign S-TAGs to FCs
// If network configuration is there, get tags from corresponding ports
// else generate unique tags to be used
// FIXME: This was supposed to be done in the validateFc method
// FIXME: but we need a vlanId here already, so that S-TAGs can be assigned below among paired INNIs/ENNIs
Set<VlanId> excludedVlans = usedVlans();
evc.fcSet().forEach(fc -> {
Optional<VlanId> cfgVlanId = getCfgVlan(fc);
if (cfgVlanId.isPresent()) {
fc.setVlanId(cfgVlanId.get());
} else {
fc.setVlanId(generateVlanId(excludedVlans));
}
excludedVlans.add(fc.vlanId());
});
// For each INNI/ENNI of each FC, find the paired INNI/ENNI and assign S-TAG according to the other FC's vlanId
for (CarrierEthernetForwardingConstruct fc : evc.fcSet()) {
for (CarrierEthernetLogicalTerminationPoint ltp : fc.ltpSet()) {
if (!ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
// Find the cp at the other end of the link
Link link = linkService.getEgressLinks(ltp.ni().cp()).iterator().next();
String ltpId = link.dst().deviceId().toString() + "/" + link.dst().port().toString();
// Find the corresponding FC - assuming LTP ids are the same as connect point ids
CarrierEthernetForwardingConstruct neighborFc = getFcFromLtpId(ltpId, evc.fcSet());
if (neighborFc != null) {
if (ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.INNI)) {
((CarrierEthernetInni) ltp.ni()).setSVlanId(neighborFc.vlanId());
} else if (ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.ENNI)) {
((CarrierEthernetEnni) ltp.ni()).setSVlanId(neighborFc.vlanId());
}
}
}
}
}
// Install the constituent FCs
evc.fcSet().forEach(fc -> {
// Increment the FC refCount
fc.refCount().incrementAndGet();
installFc(fc);
});
// Update the EVC UNI set based on the LTPs used during FC connectivity
Set<CarrierEthernetUni> usedUniSet = new HashSet<>();
evc.fcSet().forEach(fc -> usedUniSet.addAll(fc.uniSet()));
evc.setUniSet(usedUniSet);
// Determine EVC state based on the state of the constituent FCs
evc.setState(CarrierEthernetVirtualConnection.State.ACTIVE);
Iterator<CarrierEthernetForwardingConstruct> fcIt = evc.fcSet().iterator();
while (fcIt.hasNext()) {
CarrierEthernetForwardingConstruct fc = fcIt.next();
evc.setState(CarrierEthernetVirtualConnection.State.valueOf(fc.state().name()));
if (!evc.isActive()) {
break;
}
}
if (evc.isActive()) {
// If EVC installation was successful, then register the EVC
evcMap.put(evc.id(), evc);
} else {
// If EVC installation was not successful, then do not register the EVC and rollback FC installations
evc.fcSet().forEach(fc -> {
// Decrement the FC refCount to make removal possible
fc.refCount().decrementAndGet();
removeFc(fc.id());
});
}
return evc;
}
/**
* Creates a single FC out of an EVC.
*
* @param evc the EVC representation
* @return the equivalent FC
*/
private CarrierEthernetForwardingConstruct fcFromEvc(CarrierEthernetVirtualConnection evc) {
Set<CarrierEthernetLogicalTerminationPoint> ltpSet = new HashSet<>();
evc.uniSet().forEach(uni -> ltpSet.add(new CarrierEthernetLogicalTerminationPoint(null, uni)));
return CarrierEthernetForwardingConstruct.builder()
.type(evc.type())
.ltpSet(ltpSet)
.build();
}
/**
* Fragments an EVC into multiple FCs.
*
* @param evc the EVC representation
* @return the set of FCs constituting the EVC
*/
private Set<CarrierEthernetForwardingConstruct> fragmentEvc(CarrierEthernetVirtualConnection evc) {
Set<CarrierEthernetForwardingConstruct> fcSet = new HashSet<>();
// Each LTP can only belong to a single FC, hence using LTP_id -> LTP_set map
Map<String, Set<CarrierEthernetLogicalTerminationPoint>> ltpSetMap = new HashMap<>();
// Temporary set to browse through all EVC UNI pairs
Set<CarrierEthernetUni> tempUniSet = new HashSet<>(evc.uniSet());
Iterator<CarrierEthernetUni> uniIt1 = tempUniSet.iterator();
while (uniIt1.hasNext()) {
CarrierEthernetUni uni1 = uniIt1.next();
// Iterate through all the remaining NIs
Iterator<CarrierEthernetUni> uniIt2 = tempUniSet.iterator();
while (uniIt2.hasNext()) {
CarrierEthernetUni uni2 = uniIt2.next();
// Skip equals
if (uni1.equals(uni2)) {
continue;
}
// Do not establish connectivity between leaf NIs
// (applies to Rooted_Multipoint)
if (uni1.role().equals(CarrierEthernetUni.Role.LEAF)
&& uni2.role().equals(CarrierEthernetUni.Role.LEAF)) {
continue;
}
// Note: INNIs should always appear in pairs
List<Pair<CarrierEthernetLogicalTerminationPoint,
CarrierEthernetLogicalTerminationPoint>> ltpPairList
= new ArrayList<>();
// If uni1 and uni2 are on same device, skip path calculation
// and directly generate a single LTP pair to be used below
if (uni1.cp().deviceId().equals(uni2.cp().deviceId())) {
ltpPairList.add(Pair.of(new CarrierEthernetLogicalTerminationPoint(null, uni1),
new CarrierEthernetLogicalTerminationPoint(null, uni2)));
} else {
// Calculate path assuming return paths are the same
// TODO: Handle the congruent paths case?
Set<Path> paths;
if (evc.type().equals(CarrierEthernetVirtualConnection.Type.POINT_TO_POINT)) {
// For point-to-point connectivity use the pre-calculated paths
// to make sure the shortest paths are chosen
paths = pathService.getPaths(uni1.cp().deviceId(), uni2.cp().deviceId());
} else {
// Recalculate path so that it's over the pre-calculated spanning tree
// FIXME: Find a more efficient way (avoid recalculating paths)
paths = pathService.getPaths(uni1.cp().deviceId(), uni2.cp().deviceId(),
new CarrierEthernetSpanningTreeWeight(topologyService));
}
// Just select any of the returned paths
// TODO: Select path in more sophisticated way and return null
// if any of the constraints cannot be met
Path path = paths.iterator().hasNext() ? paths.iterator().next() : null;
if (path == null) {
return null;
}
List<Link> links = new ArrayList<>();
links.add(createEdgeLink(uni1.cp(), true));
links.addAll(path.links());
links.add(createEdgeLink(uni2.cp(), false));
////////////////////////////////////////////////////////////
// Get LTP pairs of ingress/egress NIs along the link path
// (non-LTP connect points are ignored)
////////////////////////////////////////////////////////////
CarrierEthernetLogicalTerminationPoint srcLtp = null, dstLtp = null;
// These are the roles that will be used for all pairs found below
CarrierEthernetLogicalTerminationPoint.Role srcLtpRole, dstLtpRole;
// The source in any pair will always have the same role as the LTP from which the paths starts
srcLtpRole = CarrierEthernetLogicalTerminationPoint.Role.valueOf((uni1).role().name());
// The destination in any pair will always have the same role as the LTP at which the path ends
dstLtpRole = CarrierEthernetLogicalTerminationPoint.Role.valueOf((uni2).role().name());
for (int i = 0; i < links.size(); i++) {
// Try to get the destination LTP of a pair
if (srcLtp != null && i != 0) {
// If this is the last, use existing EVC UNI, else create a new FC LTP and set Role
dstLtp = (i == links.size() - 1) ?
new CarrierEthernetLogicalTerminationPoint(null, uni2) :
fcLtpFromCp(links.get(i).src(), dstLtpRole);
}
if (dstLtp != null) {
// Create a new LTP pair and null the srcLtp
// so that we can continue searching for a new pair
ltpPairList.add(Pair.of(srcLtp, dstLtp));
srcLtp = null;
}
// Try to get the source LTP of a pair
if (srcLtp == null && i != links.size() - 1) {
// If this is the first, use existing EVC UNI, else create a new FC LTP and set Role
srcLtp = (i == 0) ?
new CarrierEthernetLogicalTerminationPoint(null, uni1) :
fcLtpFromCp(links.get(i).dst(), srcLtpRole);
}
}
}
////////////////////////////////////////////////////////////////
// Go through all the LTP pairs found and map each LTP to a set
// of LTPs (create it if it doesn't exist)
////////////////////////////////////////////////////////////////
// Note: Each LTP can only belong to a single set, so each set
// will eventually correspond to an FC
ltpPairList.forEach(ltpPair -> {
CarrierEthernetLogicalTerminationPoint ltp1 = ltpPair.getLeft();
CarrierEthernetLogicalTerminationPoint ltp2 = ltpPair.getRight();
if (ltpSetMap.containsKey(ltp1.id()) && !ltpSetMap.containsKey(ltp2.id())) {
// If one of the LTPs is already contained in a set, add the other one as well in that set
ltpSetMap.get(ltp1.id()).add(ltp2);
ltpSetMap.put(ltp2.id(), ltpSetMap.get(ltp1.id()));
} else if (ltpSetMap.containsKey(ltp2.id()) & !ltpSetMap.containsKey(ltp1.id())) {
// If one of the LTPs is already contained in a set, add the other one as well in that set
ltpSetMap.get(ltp2.id()).add(ltp1);
ltpSetMap.put(ltp1.id(), ltpSetMap.get(ltp2.id()));
} else if (!ltpSetMap.containsKey(ltp1.id()) && !ltpSetMap.containsKey(ltp2.id())) {
// Create a new LTP set containing the two LTPs and map both to it
ltpSetMap.put(ltp1.id(), Sets.newHashSet(ltp1, ltp2));
ltpSetMap.put(ltp2.id(), ltpSetMap.get(ltp1.id()));
}
});
}
// Remove UNI from temporary set so that each pair is visited only once
uniIt1.remove();
}
//////////////////////////////////////////////////////////////////////////////////
// Go through all unique LTP sets generated above and create the corresponding FCs
//////////////////////////////////////////////////////////////////////////////////
ltpSetMap.values().stream().collect(Collectors.toSet()).forEach(ltpSet -> {
CarrierEthernetForwardingConstruct.Builder fcBuilder =
CarrierEthernetForwardingConstruct.builder().ltpSet(ltpSet);
// Type is determined by number and type of LTPs in each set
CarrierEthernetVirtualConnection.Type fcType =
ltpSet.size() == 2 ? CarrierEthernetVirtualConnection.Type.POINT_TO_POINT
: CarrierEthernetConnection.Type.MULTIPOINT_TO_MULTIPOINT;
// If one of the LTPs is LEAF, indicate FC as ROOT_MULTIPOINT
for (CarrierEthernetLogicalTerminationPoint ltp : ltpSet) {
if (ltp.role().equals(CarrierEthernetLogicalTerminationPoint.Role.LEAF)) {
fcType = CarrierEthernetConnection.Type.ROOT_MULTIPOINT;
break;
}
}
fcSet.add(fcBuilder.type(fcType).build());
log.info("Created ForwardingConstruct comprising LogicalTerminationPoints {}",
ltpSet.stream()
.map(CarrierEthernetLogicalTerminationPoint::id)
.collect(Collectors.toList()));
});
return fcSet;
}
@Override
public CarrierEthernetVirtualConnection updateEvc(CarrierEthernetVirtualConnection evc) {
// Just checking again
if (evcMap.containsKey(evc.id())) {
log.info("Updating existing EVC {}", evc.id());
removeEvc(evc.id());
}
return installEvc(evc);
}
/**
* Applies FC- specific LTP attributes to global LTPs or adds them to the global LTP map if not there.
*
* @param ltpSet set of FC-specific LTPs the attributes of which will be applied to the global LTPs
*/
private void applyFcToGlobalLtps(Set<CarrierEthernetLogicalTerminationPoint> ltpSet) {
ltpSet.forEach(ltp -> {
if (!(ltpMap.keySet().contains(ltp.id()))) {
// Just add the LTP as it appears at the FC
addGlobalLtp(ltp);
} else {
// Add LTP resources (BWP, CE-VLAN ID, S-TAG) to existing global LTP
ltpMap.get(ltp.id()).ni().addEcNi(ltp.ni());
// Update config identifier
ltpMap.get(ltp.id()).ni().setCfgId(ltp.ni().cfgId());
}
});
}
/**
* Removes bandwidth profiles from the UNIs of an FC.
*
* @param fc the FC representation
*/
// TODO: Remove LTPs if needed from the global LTP/UNI map
private void removeFcFromGlobalLtps(CarrierEthernetForwardingConstruct fc) {
// TODO: Check if the bandwidth profile really needs to be removed (e.g. may be CoS)
ceProvisioner.removeBandwidthProfiles(fc);
// Remove LTP resources (BWP, CE-VLAN ID, S-TAG) from corresponding global LTPs
fc.ltpSet().forEach(ltp -> ltpMap.get(ltp.id()).ni().removeEcNi(ltp.ni()));
}
@Override
public void removeAllEvcs() {
evcMap.keySet().forEach(evcId -> removeEvc(evcId));
}
@Override
public void removeEvc(String evcId) {
if (evcMap.containsKey(evcId)) {
CarrierEthernetVirtualConnection evc = evcMap.get(evcId);
evc.fcSet().forEach(fc -> {
// Decrement the FC refCount to make removal possible
fc.refCount().decrementAndGet();
removeFc(fc.id());
});
// Avoid excessively incrementing EVC ids
nextEvcShortId = evc.shortId() < nextEvcShortId ? evc.shortId() : nextEvcShortId;
evcMap.remove(evcId);
}
}
/**
* Verify the validity of an FC representation taking also into account current network status.
*
* @param fc the provided FC representation
* @return a valid, potentially modified FC representation, or null if the FC could not be validated
*/
private CarrierEthernetForwardingConstruct validateFc(CarrierEthernetForwardingConstruct fc) {
// Try to set a unique VLAN id for the FC unless the EVC is being updated
// TODO: Add different connectivity types
// FIXME: This is an extra check to be able to generate/set VLAN id for FC before calling installFc
if (fc.vlanId() == null) {
fc.setVlanId(generateVlanId(usedVlans()));
}
if (fc.vlanId() == null) {
log.error("No available VLAN id found.");
return null;
}
// Generate and set unique FC id
fc.setId(generateFcId(fc));
Set<CarrierEthernetLogicalTerminationPoint> validatedLtpSet = new HashSet<>();
// Check the NIs of the FC, possibly removing NIs that are incompatible with existing ones
Iterator<CarrierEthernetLogicalTerminationPoint> ltpIt = fc.ltpSet().iterator();
while (ltpIt.hasNext()) {
CarrierEthernetLogicalTerminationPoint ltp = ltpIt.next();
boolean ltpValidated = true;
if (ltp.type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
CarrierEthernetUni uni = (CarrierEthernetUni) ltp.ni();
// Change the name of the UNI's BWP to the FC name if it is an EVC BWP
if (uni.bwp().type().equals(CarrierEthernetBandwidthProfile.Type.EVC)) {
// FIXME: Find a way to use the EVC name instead
uni.bwp().setId(fc.id());
}
}
// Check first if LTP already exists by checking against the global LTP Map
if (ltpMap.keySet().contains(ltp.id())) {
CarrierEthernetNetworkInterface existingNi = ltpMap.get(ltp.id()).ni();
// Check if the FC-specific NI is compatible with the global one
if (!(existingNi.validateEcNi(ltp.ni()))) {
ltpValidated = false;
}
}
if (!ltpValidated) {
// If EVC is of ROOT_MULTIPOINT type and we have removed the root, return null
if (fc.type() == CarrierEthernetForwardingConstruct.Type.ROOT_MULTIPOINT &&
ltp.role() == CarrierEthernetLogicalTerminationPoint.Role.ROOT) {
log.error("Root LTP could not be added to %s FC.", fc.type().name());
return null;
}
log.warn("LTP {} could not be added to FC.", ltp.id());
continue;
} else {
// Add LTP to FC description
validatedLtpSet.add(ltp);
}
}
fc.setLtpSet(validatedLtpSet);
return fc;
}
@Override
public CarrierEthernetForwardingConstruct installFc(CarrierEthernetForwardingConstruct fc) {
// If FC already exists, remove it and reestablish with new parameters
if (fc.id() != null && fcMap.containsKey(fc.id())) {
return updateFc(fc);
} else {
fc.setId(null);
}
if (validateFc(fc) == null) {
log.error("FC could not be installed, please check log for details.");
return null;
}
// Create BW profiles first so that they will be available if needed during the connectivity phase
ceProvisioner.createBandwidthProfiles(fc);
ceProvisioner.setupConnectivity(fc);
// If connectivity was not successful, then do not register the FC and do not apply BW profiles
// If not, the BW profiles that were created earlier need to be removed
if (fc.state().equals(CarrierEthernetForwardingConstruct.State.ACTIVE)) {
// Apply BWP-related resources (e.g. Meters) to the packet switches
ceProvisioner.applyBandwidthProfiles(fc);
// Apply the BWPs of the FC UNIs to the global UNIs, creating them if needed
//applyEvcToGlobalUnis(fc.uniSet());
applyFcToGlobalLtps(fc.ltpSet());
// Increment the global LTP and corresponding NI refCount
fc.ltpSet().forEach(ltp -> ltpMap.get(ltp.id()).refCount().incrementAndGet());
fcMap.put(fc.id(), fc);
} else {
ceProvisioner.removeBandwidthProfiles(fc);
}
return fc;
}
@Override
public CarrierEthernetForwardingConstruct updateFc(CarrierEthernetForwardingConstruct fc) {
// Just checking again
if (fcMap.containsKey(fc.id())) {
log.info("Updating existing FC {}", fc.id());
// Keep the VLAN ID of the original FC
fc.setVlanId(fcMap.get(fc.id()).vlanId());
// FIXME: Currently FC update only possible for standalone FCs
removeFc(fc.id());
}
return installFc(fc);
}
@Override
public void removeAllFcs() {
fcMap.keySet().forEach(fcId -> removeFc(fcId));
}
@Override
public CarrierEthernetForwardingConstruct removeFc(String fcId) {
if (fcMap.containsKey(fcId)) {
CarrierEthernetForwardingConstruct fc = fcMap.get(fcId);
if (fc.refCount().get() != 0) {
log.warn("Could not remove FC {}: RefCount is not zero", fc.id());
return null;
}
ceProvisioner.removeConnectivity(fc);
ceProvisioner.removeBandwidthProfiles(fc);
removeFcFromGlobalLtps(fc);
// Avoid excessively incrementing FC VLAN ids
nextVlanId = (fcMap.get(fcId).vlanId().toShort() < nextVlanId ?
fcMap.get(fcId).vlanId().toShort() :
nextVlanId);
// Decrement the global LTP and corresponding NI refCount
fcMap.get(fcId).ltpSet().forEach(ltp -> ltpMap.get(ltp.id()).refCount().decrementAndGet());
fcMap.remove(fcId);
return fc;
}
return null;
}
/**
* Returns the unique S-TAGs currently used by FCs across the CE network.
*
* @return the S-TAGs currently used
*/
private Set<VlanId> usedVlans() {
return fcMap.values().stream().map(CarrierEthernetForwardingConstruct::vlanId)
.collect(Collectors.toSet());
}
/**
* Generates a new vlanId excluding the provided ones.
*
* @param excludedVlans the vlanIds that are not allowed
* @return the generated vlanId; null if none found
*/
private VlanId generateVlanId(Set<VlanId> excludedVlans) {
// If all vlanIds are being used return null, else try to find the next available one
if (excludedVlans.size() < VlanId.MAX_VLAN - 1) {
while (excludedVlans.contains(VlanId.vlanId(nextVlanId))) {
// Get next valid short
nextVlanId = (nextVlanId >= VlanId.MAX_VLAN || nextVlanId <= 0 ?
1 : (short) (nextVlanId + 1));
}
}
return excludedVlans.contains(VlanId.vlanId(nextVlanId)) ?
null : VlanId.vlanId(nextVlanId);
}
/**
* Generates a unique vlanId in the context of the CE app.
*
* @return the generated vlanId or null if none found
*/
private Short generateEvcShortId() {
List<Short> evcShortIdList = evcMap.values()
.stream()
.map(evc -> Short.valueOf(evc.shortId()))
.collect(Collectors.toList());
// If all vlanIds are being used return null, else try to find the next available one
if (evcShortIdList.size() < Short.MAX_VALUE - 1) {
while (evcShortIdList.contains(nextEvcShortId)) {
// Get next valid short
nextEvcShortId =
(nextEvcShortId >= Short.MAX_VALUE || nextEvcShortId <= 0 ? 1 : (short) (nextEvcShortId + 1));
}
}
return evcShortIdList.contains(nextEvcShortId) ? null : nextEvcShortId;
}
/**
* Generates a unique EVC id in the context of the CE app.
*
* @param evc the EVC representation
* @return the generated EVC id or null if none found
*/
private String generateEvcId(CarrierEthernetVirtualConnection evc) {
// TODO: Add different connectivity types
String tmpType;
if (evc.type().equals(CarrierEthernetVirtualConnection.Type.POINT_TO_POINT)) {
tmpType = "Line";
} else if (evc.type().equals(CarrierEthernetVirtualConnection.Type.MULTIPOINT_TO_MULTIPOINT)) {
tmpType = "LAN";
} else {
tmpType = "Tree";
}
return "E" + (evc.isVirtual() ? "V" : "") + "P-" + tmpType + "-" +
evc.shortId().toString();
}
/**
* Generates a unique FC id in the context of the CE app.
*
* @param fc the FC representation
* @return the generated FC id or null if none found
*/
private String generateFcId(CarrierEthernetForwardingConstruct fc) {
// TODO: Add different connectivity types
return "FC-" + fc.vlanId().toString();
}
@Override
public CarrierEthernetLogicalTerminationPoint removeGlobalLtp(String ltpId) {
if (!ltpMap.containsKey(ltpId)) {
log.warn("Could not remove LTP {}: Does not exist", ltpId);
return null;
}
if (ltpMap.get(ltpId).refCount().get() != 0) {
log.warn("Could not remove LTP {}: RefCount is not zero", ltpId);
return null;
}
// Remove LTP from ltpMap and (if needed) UNI from uniMap
CarrierEthernetLogicalTerminationPoint ltp = ltpMap.remove(ltpId);
// Add LTP to removed set
removedLtpSet.add(ltpId);
if (ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
removeGlobalUni(ltp.ni().id());
// Add UNI to removed set
// TODO: Check if this is right
removedUniSet.add(ltp.ni().id());
}
// Find cp at other end of link and try to remove both LTPs - assuming LTP ids are the same as connect point ids
if (ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.INNI)) {
Link link = linkService.getEgressLinks(ltp.ni().cp()).iterator().next();
String pairLtpId = link.dst().deviceId().toString() + "/" + link.dst().port().toString();
ltpMap.remove(pairLtpId);
// Add LTP to removed set
removedLtpSet.add(pairLtpId);
}
return ltp;
}
@Override
public CarrierEthernetUni removeGlobalUni(String uniId) {
if (!uniMap.containsKey(uniId)) {
log.warn("Could not remove UNI {}: Does not exist", uniId);
return null;
}
if (uniMap.get(uniId).refCount().get() != 0) {
log.warn("Could not remove UNI {}: RefCount is not zero", uniId);
return null;
}
// Remove UNI from uniMap and corresponding LTP (if any) from ltpMp
CarrierEthernetUni uni = uniMap.remove(uniId);
// FIXME: For now, find LTP assuming ltpId is the same as uniId
// Note: If refCount for UNI is not zero, then it should be for the corresponding LTP as well
ltpMap.remove(uniId);
// Add UNI and LTP to removed set
removedUniSet.add(uniId);
removedLtpSet.add(uniId);
return uni;
}
@Override
public Set<CarrierEthernetUni> getUnisFromTopo(boolean excludeAdded, boolean includeRemoved) {
CarrierEthernetUni uni;
Set<CarrierEthernetUni> uniSet = new HashSet<>();
// Generate the device ID/port number identifiers
for (Device device : deviceService.getDevices()) {
for (Port port : deviceService.getPorts(device.id())) {
if (!port.number().isLogical()) {
String cpString = device.id().toString() + "/" + port.number();
ConnectPoint cp = ConnectPoint.deviceConnectPoint(cpString);
uni = generateUni(cp);
// Check if UNI was generated and whether it's currently removed
if (uni != null
&& (includeRemoved || !removedUniSet.contains(uni.id()))
&& (!excludeAdded || !uniMap.containsKey(uni.id()))) {
uniSet.add(uni);
}
}
}
}
return uniSet;
}
@Override
public CarrierEthernetUni generateUni(ConnectPoint cp) {
String uniId = cp.deviceId().toString() + "/" + cp.port().toString();
if (deviceService.getDevice(cp.deviceId()) == null) {
log.error("Could not generate UNI {}: Invalid deviceId {}", uniId, cp.deviceId());
return null;
}
if (deviceService.getPort(cp.deviceId(), cp.port()) == null) {
log.error("Could not generate UNI {}: Invalid port {} at device {}", uniId, cp.port(), cp.deviceId());
return null;
}
if (!deviceService.getDevice(cp.deviceId()).type().equals(Device.Type.SWITCH)) {
log.debug("Could not generate UNI {}: Device {} is not a switch", uniId, cp.deviceId());
return null;
}
Port port = deviceService.getPort(cp.deviceId(), cp.port());
if (!port.isEnabled()) {
log.debug("Could not generate UNI {}: Port {} is not enabled", uniId, port.number().toString());
return null;
}
if (validateLtpType(cp, CarrierEthernetNetworkInterface.Type.UNI) == null) {
return null;
}
return CarrierEthernetUni.builder()
.cp(cp)
.cfgId(uniId)
.build();
}
@Override
public CarrierEthernetUni addGlobalUni(CarrierEthernetUni uni) {
// Add UNI only if it's not already there. If corresponding LTP already exists, link them, otherwise create it
if (!uniMap.containsKey(uni.id())) {
// Add LTP only if it's not already there
// FIXME: Assumes LTP and UNI id are the same
if (!ltpMap.containsKey(uni.id())) {
ltpMap.put(uni.id(), new CarrierEthernetLogicalTerminationPoint(uni.id(), uni));
// Remove LTP from deleted set
removedLtpSet.remove(uni.id());
}
uniMap.put(uni.id(), uni);
// Remove UNI from deleted set
removedUniSet.remove(uni.id());
return uni;
} else {
return null;
}
}
@Override
public Set<CarrierEthernetLogicalTerminationPoint> getLtpsFromTopo(boolean excludeAdded, boolean includeRemoved) {
CarrierEthernetLogicalTerminationPoint ltp;
Set<CarrierEthernetLogicalTerminationPoint> ltpSet = new HashSet<>();
// Generate the device ID/port number identifiers
for (Device device : deviceService.getDevices()) {
for (Port port : deviceService.getPorts(device.id())) {
if (!port.number().isLogical()) {
String cpString = device.id().toString() + "/" + port.number();
ConnectPoint cp = ConnectPoint.deviceConnectPoint(cpString);
ltp = generateLtp(cp, null);
// Check if LTP was generated and whether it's currently removed
if (ltp != null
&& (includeRemoved || !removedLtpSet.contains(ltp.id()))
&& (!excludeAdded || !ltpMap.containsKey(ltp.id()))) {
// Check additionally if associated UNI is currently removed
if (!(ltp.ni() instanceof CarrierEthernetUni) || !removedUniSet.contains(ltp.ni().id())) {
ltpSet.add(ltp);
}
}
}
}
}
return ltpSet;
}
@Override
public CarrierEthernetLogicalTerminationPoint generateLtp(ConnectPoint cp,
CarrierEthernetNetworkInterface.Type ltpType) {
String ltpId = cp.deviceId().toString() + "/" + cp.port().toString();
if (deviceService.getDevice(cp.deviceId()) == null) {
log.error("Could not generate LTP {}: Invalid deviceId {}", ltpId, cp.deviceId());
return null;
}
if (deviceService.getPort(cp.deviceId(), cp.port()) == null) {
log.error("Could not generate LTP {}: Invalid port {} at device {}", ltpId, cp.port(), cp.deviceId());
return null;
}
if (!deviceService.getDevice(cp.deviceId()).type().equals(Device.Type.SWITCH)) {
log.debug("Could not generate LTP {}: Device {} is not a switch", ltpId, cp.deviceId());
return null;
}
Port port = deviceService.getPort(cp.deviceId(), cp.port());
if (!port.isEnabled()) {
log.debug("Could not generate LTP {}: Port {} is not enabled", ltpId, port.number().toString());
return null;
}
ltpType = validateLtpType(cp, ltpType);
if (ltpType == null) {
log.warn("Could not generate LTP {}: Type could not be validated", ltpId, port.number().toString());
return null;
}
return new CarrierEthernetLogicalTerminationPoint(cp, ltpId, ltpType, null);
}
/**
* Validates whether the provided connect point can be associated with an LTP of the provided type.
*
* Conditions for validating the LTP type:
* - If UNI: ConnectPoint is not associated with any link
* - If INNI/ENNI: ConnectPoint is associated with a link
*
* @param cp the connect point associated with the LTP to be validated
* @param ltpType the type of the LTP to be validated or null in case a type is to be decided by the method
* @return the ltpType if validation succeeded, a new type depending on cp and topo, or null if validation failed
*/
private CarrierEthernetNetworkInterface.Type validateLtpType(
ConnectPoint cp, CarrierEthernetNetworkInterface.Type ltpType) {
if (linkService.getEgressLinks(cp).isEmpty() && linkService.getIngressLinks(cp).isEmpty()) {
// A connect point can be a UNI only if it doesn't belong to any link
if (ltpType == null) {
// If provided type is null, decide about the LTP type based on connectivity
return CarrierEthernetNetworkInterface.Type.UNI;
} else if (ltpType.equals(CarrierEthernetNetworkInterface.Type.UNI)) {
// Validate type
return ltpType;
} else {
return null;
}
} else {
// A connect point can be an INNI or ENNI only if it belongs to a link
if (ltpType == null) {
// If provided type is null, decide about the LTP type based on connectivity
return CarrierEthernetNetworkInterface.Type.INNI;
} else if (ltpType.equals(CarrierEthernetNetworkInterface.Type.INNI) ||
ltpType.equals(CarrierEthernetNetworkInterface.Type.ENNI)) {
// Validate type
return ltpType;
} else {
return null;
}
}
}
@Override
public CarrierEthernetLogicalTerminationPoint addGlobalLtp(CarrierEthernetLogicalTerminationPoint ltp) {
// If LTP contains a UNI, add it only if it's not already there, else point to the existing UNI
// FIXME: Assumes LTP and UNI id are the same
if (ltp.ni() != null && ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
if (!uniMap.containsKey(ltp.ni().id())) {
uniMap.put(ltp.ni().id(), (CarrierEthernetUni) ltp.ni());
// Remove UNI from deleted set
removedUniSet.remove(ltp.id());
} else {
ltp.setNi(uniMap.get(ltp.ni().id()));
}
}
// Add LTP only if it's not already there
if (!ltpMap.containsKey(ltp.id())) {
// Try to create and add INNI LTP at other end of link as well
if (ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.INNI)) {
Link link = linkService.getEgressLinks(ltp.ni().cp()).iterator().next();
CarrierEthernetLogicalTerminationPoint pairLtp =
generateLtp(link.dst(), CarrierEthernetNetworkInterface.Type.INNI);
if (pairLtp == null) {
return null;
}
if (!ltpMap.containsKey(pairLtp.id())) {
ltpMap.put(pairLtp.id(), pairLtp);
} else {
return null;
}
}
ltpMap.put(ltp.id(), ltp);
// Remove LTP from deleted set
removedLtpSet.remove(ltp.id());
return ltp;
} else {
return null;
}
}
/**
* Utility method to obtain an FC-specific LTP (UNI/INNI or ENNI) associated with a connect point.
*
* @param cp the connect point to check
* @return a new FC-specific LTP associated with cp if the corresponding global LTP exists or null otherwise
*/
private CarrierEthernetLogicalTerminationPoint fcLtpFromCp(ConnectPoint cp,
CarrierEthernetLogicalTerminationPoint.Role ltpRole) {
// Check first if cp is associated with a device
if (cp.deviceId() == null) {
return null;
}
// Assuming LTP id is the same as the connect point id
String cpId = cp.deviceId().toString() + "/" + cp.port().toString();
if (ltpMap.containsKey(cpId)) {
CarrierEthernetLogicalTerminationPoint ltp =
new CarrierEthernetLogicalTerminationPoint(cp, cpId, ltpMap.get(cpId).type(), ltpRole);
return ltp;
} else {
return null;
}
}
/**
* Utility method to obtain the first FC in a set which contains the LTP with the provided id.
*
* @param ltpId the LTP id to search
* @param fcSet the FC set to search
* @return the first FC found in fcSet which contains an LTP with id ltpId, or null if no such FC is found
*/
// FIXME: Find more efficient way to do that
private CarrierEthernetForwardingConstruct getFcFromLtpId(String ltpId,
Set<CarrierEthernetForwardingConstruct> fcSet) {
// Get the first FC that contains the LTP with the provided id
for (CarrierEthernetForwardingConstruct fc : fcSet) {
if (!fc.ltpSet().stream().filter(ltp -> ltp.id().equals(ltpId)).collect(Collectors.toList()).isEmpty()) {
return fc;
}
}
return null;
}
@Override
public void setEvcFragmentation(boolean evcFragmentationEnabled) {
prevEvcFragmentationStatus = this.evcFragmentationEnabled;
this.evcFragmentationEnabled = evcFragmentationEnabled;
}
@Override
public boolean getEvcFragmentation() {
return evcFragmentationEnabled;
}
@Override
public void resetEvcFragmentation() {
this.evcFragmentationEnabled = prevEvcFragmentationStatus;
}
/**
* Returns the VLAN tag associated with an FC via network configuration.
*
* The VLAN tag to be selected should be configured in at least one of the
* FC LTPs and no different tag should be present in the rest of the FC LTPs.
*
* @param fc the FC to check
* @return an Optional object with the VLAN to be associated with the FC if
* one was found; an empty Optional object otherwise
*/
private Optional<VlanId> getCfgVlan(CarrierEthernetForwardingConstruct fc) {
VlanId cfgVlan = null;
for (CarrierEthernetLogicalTerminationPoint ltp : fc.ltpSet()) {
VlanId tmpVlan = portVlanMap.get(ltp.cp());
if (tmpVlan == null) {
continue;
} else if (cfgVlan != null && cfgVlan != tmpVlan) {
log.warn("Multiple configured S-TAGs for the same FC");
return Optional.empty();
} else {
cfgVlan = tmpVlan;
}
}
return cfgVlan == null ? Optional.empty() : Optional.of(cfgVlan);
}
private class InternalNetworkConfigListener implements NetworkConfigListener {
/**
* Negative events.
*/
private final EnumSet<NetworkConfigEvent.Type> negative
= EnumSet.of(NetworkConfigEvent.Type.CONFIG_UNREGISTERED,
NetworkConfigEvent.Type.CONFIG_REMOVED);
/**
* Actual configuration events.
*/
private final EnumSet<NetworkConfigEvent.Type> actualConfig
= EnumSet.of(NetworkConfigEvent.Type.CONFIG_ADDED,
NetworkConfigEvent.Type.CONFIG_REMOVED,
NetworkConfigEvent.Type.CONFIG_UPDATED);
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return event.configClass().equals(PortVlanConfig.class) &&
actualConfig.contains(event.type());
}
@Override
public void event(NetworkConfigEvent event) {
if (!isRelevant(event)) {
return;
}
ConnectPoint cp = (ConnectPoint) event.subject();
PortVlanConfig config = networkConfigService.getConfig(cp, PortVlanConfig.class);
if (config == null) {
log.info("VLAN tag config is removed from port {}", cp);
portVlanMap.remove(cp);
return;
}
if (config.portVlanId().isPresent() && !negative.contains(event.type())) {
VlanId assignedVlan = config.portVlanId().get();
if (usedVlans().contains(assignedVlan)) {
log.warn("VLAN tag {} is already used in the CE network", assignedVlan);
} else {
log.info("VLAN tag {} is assigned to port {}", assignedVlan, cp);
portVlanMap.put(cp, assignedVlan);
}
} else {
log.info("VLAN tag is removed from port {}", cp);
portVlanMap.remove(cp);
}
}
}
}