Java tutorial
/* * Copyright 2016 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.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.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; @Component(immediate = true) @Service(value = CarrierEthernetManager.class) public class CarrierEthernetManager { 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 CarrierEthernetProvisioner 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(); } /** * Returns the map of installed EVCs. * * @return map of installed EVCs */ public Map<String, CarrierEthernetVirtualConnection> evcMap() { return this.evcMap; } // TODO: Add method to remove a UNI from an already installed EVC /** * Get an installed EVC using its id. * * @param evcId the EVC id * @return the EVC representation or null if the EVC doesn't exist */ public CarrierEthernetVirtualConnection getEvc(String evcId) { return ((evcMap.get(evcId) == null) ? null : evcMap.get(evcId)); } /** * Get an installed FC using its id. * * @param fcId the FC id * @return the FC representation or null if the EVC doesn't exist */ public CarrierEthernetForwardingConstruct getFc(String fcId) { return ((fcMap.get(fcId) == null) ? null : fcMap.get(fcId)); } /** * Get the map containing all installed FCs * * @return the FC map */ public Map<String, CarrierEthernetForwardingConstruct> fcMap() { return fcMap; } /** * Get the map containing all global LTPs * * @return the global LTP map */ public Map<String, CarrierEthernetLogicalTerminationPoint> ltpMap() { return ltpMap; } /** * Get the map containing all global UNIs * * @return the global UNI map */ 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; } /** * Establish connectivity according to the EVC type (E-Line, E-Tree, E-LAN) and the EVC parameters. * * @param evc the EVC representation * @return the (potentially modified) EVC that was installed or null in case of failure */ 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 */ 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 */ 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; } /** * Reestablish connectivity for an existing EVC. * * @param evc the updated EVC definition * @return the (potentially modified) EVC that was installed or null if EVC connectivity could not be established */ 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())); } /** * Removes all installed EVCs and the associated resources. * * This will be called either from the deactivate method or as a response to a CLI/REST command. * */ public void removeAllEvcs() { evcMap.keySet().forEach(evcId -> removeEvc(evcId)); } /** * Removes all resources associated with a specific installed EVC. * * @param evcId the EVC id * */ 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; } /** * Installs all resources associated with a specific FC. * * @param fc the FC to install * @return the FC that was installed * */ 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; } /** * Reestablish connectivity for an existing FC. * * @param fc the updated FC representation * @return the possibly modified FC that was installed or null if updated FC could not be installed */ 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); } /** * Removes all resources associated with the application. * * This will be called either from the deactivate method or as a response to a CLI command. * */ public void removeAllFcs() { fcMap.keySet().forEach(fcId -> removeFc(fcId)); } /** * Removes all resources associated with a specific FC. * * @param fcId the FC id * @return the FC that was removed or null if removal failed * */ 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 * */ public 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(); } /** * Remove an LTP from the set of global LTPs, as well as the corresponding INNI LTP at the other end of the link. * * * @param ltpId the id of the LTP to be removed * @return the LTP that was removed or null in case of failure (didn't exist of refCount was not 0) * */ // TODO: Add removeAllGlobalLtps method (or command only?) 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; } /** * Remove an UNI from the set of global UNIs. * * @param uniId the id of the UNI to be removed * @return the UNI that was removed or null in case of failure (didn't exist of refCount was not 0) * */ // TODO: Add removeAllGlobalUnis method (or command only?) 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; } /** * Returns all potential UNIs from the topology. * * @param excludeAdded indicates that UNIs already added in the UNI map should not be in the returned set * @param includeRemoved indicates that UNIs explicitly removed from the UNI map should be in the returned set * @return set of all potential UNIs in the topology * */ 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; } /** * Creates a new UNI associated with the provided connect point. * * Conditions for validating an UNI: * - ConnectPoint deviceId and Port are valid * - Port is enabled * * @param cp the connect point to be associated with the generated UNI * @return a new validated UNI or null if the validation failed * */ 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 new CarrierEthernetUni(cp, uniId); } /** * Adds a potential UNI to the global UNI map if they are not already there. * * @param uni the potential UNI to add to global UNI map * @return the UNI that was added or null if UNI existed already * */ 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; } } /** * Returns all potential LTPs from the topology. * @param excludeAdded indicates that LTPs already added in the LTP map should not be in the returned set * @param includeRemoved indicates that LTPs explicitly removed from the LTP map should be in the returned set * @return set of all potential LTPs in the topology * */ 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; } /** * Creates a new LTP of the provided type and associated with the provided connect point. * * Conditions for validating an LTP: * - ConnectPoint deviceId and Port are valid * - Port is enabled * * @param cp the connect point to be associated with the generated LTP * @param ltpType the type of the LTP to be generated (UNI/INNI/ENNI) * @return a new validated LTP or null if the validation failed * */ 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; } } } /** * Adds a potential LTP and its UNI or pair INNI to the global LTP/UNI maps if they are not already there. * * @param ltp the potential LTP to add to global LTP map * @return the LTP that was added or null if it already existed * */ 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; } /** * Utility method to enable or disable EVC fragmentation into FCs. * * @param evcFragmentationEnabled true to enable fragmentation; false otherwise * */ public void setEvcFragmentation(boolean evcFragmentationEnabled) { prevEvcFragmentationStatus = this.evcFragmentationEnabled; this.evcFragmentationEnabled = evcFragmentationEnabled; } public boolean getEvcFragmentation() { return evcFragmentationEnabled; } /** * Utility method to set the EVC fragmentation flag to the value before its last change. * * */ 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); } } } }