/* * Copyright 2016-present 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.net.optical.intent.impl.compiler; import com.google.common.base.Strings; import org.apache.commons.lang3.tuple.Pair; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Modified; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.onlab.util.Tools; import org.onosproject.cfg.ComponentConfigService; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; import org.onosproject.net.AnnotationKeys; import org.onosproject.net.CltSignalType; import org.onosproject.net.ConnectPoint; import org.onosproject.net.OduSignalId; import org.onosproject.net.OduSignalType; import org.onosproject.net.OduSignalUtils; import org.onosproject.net.Port; import org.onosproject.net.TributarySlot; import org.onosproject.net.behaviour.TributarySlotQuery; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.Driver; import org.onosproject.net.driver.DriverService; import org.onosproject.net.flow.DefaultFlowRule; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.FlowRule; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flow.instructions.Instructions; import org.onosproject.net.intent.FlowRuleIntent; import org.onosproject.net.intent.Intent; import org.onosproject.net.intent.IntentCompiler; import org.onosproject.net.intent.IntentExtensionService; import org.onosproject.net.intent.IntentId; import org.onosproject.net.intent.IntentService; import org.onosproject.net.intent.OpticalCircuitIntent; import org.onosproject.net.intent.OpticalConnectivityIntent; import org.onosproject.net.intent.PathIntent; import org.onosproject.net.optical.OchPort; import org.onosproject.net.optical.OduCltPort; import org.onosproject.net.intent.IntentSetMultimap; import org.onosproject.net.resource.ResourceAllocation; import org.onosproject.net.resource.Resource; import org.onosproject.net.resource.ResourceService; import org.onosproject.net.resource.Resources; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Dictionary; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkArgument; import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView; /** * An intent compiler for {@link org.onosproject.net.intent.OpticalCircuitIntent}. */ @Component(immediate = true) public class OpticalCircuitIntentCompiler implements IntentCompiler<OpticalCircuitIntent> { private static final Logger log = LoggerFactory.getLogger(OpticalCircuitIntentCompiler.class); private static final int DEFAULT_MAX_CAPACITY = 10; @Property(name = "maxCapacity", intValue = DEFAULT_MAX_CAPACITY, label = "Maximum utilization of an optical connection.") private int maxCapacity = DEFAULT_MAX_CAPACITY; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ComponentConfigService cfgService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentExtensionService intentManager; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DeviceService deviceService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ResourceService resourceService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentSetMultimap intentSetMultimap; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentService intentService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DriverService driverService; private ApplicationId appId; @Modified public void modified(ComponentContext context) { if (context == null) { return; } Dictionary properties = context.getProperties(); //TODO for reduction check if the new capacity is smaller than the size of the current mapping String propertyString = Tools.get(properties, "maxCapacity"); //Ignore if propertyString is empty or null if (!Strings.isNullOrEmpty(propertyString)) { try { int temp = Integer.parseInt(propertyString); //Ensure value is non-negative but allow zero as a way to shutdown the link if (temp >= 0) { maxCapacity = temp; } } catch (NumberFormatException e) { //Malformed arguments lead to no change of value (user should be notified of error) log.error("The value '{}' for maxCapacity was not parsable as an integer.", propertyString, e); } } else { //Notify of empty value but do not return (other properties will also go in this function) log.error("The value for maxCapacity was set to an empty value."); } } @Activate public void activate(ComponentContext context) { deviceService = opticalView(deviceService); appId = coreService.registerApplication("org.onosproject.net.intent"); intentManager.registerCompiler(OpticalCircuitIntent.class, this); cfgService.registerProperties(getClass()); modified(context); } @Deactivate public void deactivate() { intentManager.unregisterCompiler(OpticalCircuitIntent.class); cfgService.unregisterProperties(getClass(), false); } @Override public List<Intent> compile(OpticalCircuitIntent intent, List<Intent> installable) { // Check if ports are OduClt ports ConnectPoint src = intent.getSrc(); ConnectPoint dst = intent.getDst(); Port srcPort = deviceService.getPort(src.deviceId(), src.port()); Port dstPort = deviceService.getPort(dst.deviceId(), dst.port()); checkArgument(srcPort instanceof OduCltPort); checkArgument(dstPort instanceof OduCltPort); log.debug("Compiling optical circuit intent between {} and {}", src, dst); // Release of intent resources here is only a temporary solution for handling the // case of recompiling due to intent restoration (when intent state is FAILED). // TODO: try to release intent resources in IntentManager. resourceService.release(intent.key()); // Check OduClt ports availability Resource srcPortResource = Resources.discrete(src.deviceId(), src.port()).resource(); Resource dstPortResource = Resources.discrete(dst.deviceId(), dst.port()).resource(); // If ports are not available, compilation fails if (!Stream.of(srcPortResource, dstPortResource).allMatch(resourceService::isAvailable)) { throw new OpticalIntentCompilationException("Ports for the intent are not available. Intent: " + intent); } List<Resource> ports = ImmutableList.of(srcPortResource, dstPortResource); // Check if both devices support multiplexing (usage of TributarySlots) boolean multiplexingSupported = isMultiplexingSupported(intent.getSrc()) && isMultiplexingSupported(intent.getDst()); OpticalConnectivityIntent connIntent = findOpticalConnectivityIntent(intent.getSrc(), intent.getDst(), intent.getSignalType(), multiplexingSupported); if (connIntent != null && !multiplexingSupported) { return compile(intent, src, dst, Optional.of(connIntent), ports, false); } // Create optical connectivity intent if needed - no optical intent or not enough slots available if (connIntent == null) { return compile(intent, src, dst, Optional.empty(), ports, multiplexingSupported); } List<Resource> slots = availableSlotResources(connIntent.getSrc(), connIntent.getDst(), intent.getSignalType()); if (slots.isEmpty()) { return compile(intent, src, dst, Optional.empty(), ports, true); } return compile(intent, src, dst, Optional.of(connIntent), ImmutableList.<Resource>builder() .addAll(ports).addAll(slots).build(), false); } private List<Intent> compile(OpticalCircuitIntent intent, ConnectPoint src, ConnectPoint dst, Optional<OpticalConnectivityIntent> existingConnectivity, List<Resource> resources, boolean supportsMultiplexing) { OpticalConnectivityIntent connectivityIntent; List<Resource> required; if (existingConnectivity.isPresent()) { connectivityIntent = existingConnectivity.get(); required = resources; } else { // Find OCh ports with available resources Pair<OchPort, OchPort> ochPorts = findPorts(intent.getSrc(), intent.getDst(), intent.getSignalType()); if (ochPorts == null) { throw new OpticalIntentCompilationException("Unable to find suitable OCH ports for intent " + intent); } ConnectPoint srcCP = new ConnectPoint(src.elementId(), ochPorts.getLeft().number()); ConnectPoint dstCP = new ConnectPoint(dst.elementId(), ochPorts.getRight().number()); // Create optical connectivity intent connectivityIntent = OpticalConnectivityIntent.builder() .appId(appId) // TODO New top-level Intent created and submitted // during compilation. // We'll need to track inter-Intent dependency, // but `key` field cannot be used for the purpose. .src(srcCP) .dst(dstCP) .signalType(ochPorts.getLeft().signalType()) .bidirectional(intent.isBidirectional()) .resourceGroup(intent.resourceGroup()) .build(); if (!supportsMultiplexing) { required = resources; } else { List<Resource> slots = availableSlotResources(srcCP, dstCP, intent.getSignalType()); if (slots.isEmpty()) { throw new OpticalIntentCompilationException("Unable to find Tributary Slots for intent " + intent); } required = ImmutableList.<Resource>builder().addAll(resources).addAll(slots).build(); } } if (resourceService.allocate(intent.key(), required).isEmpty()) { throw new OpticalIntentCompilationException("Unable to allocate resources for intent " + intent + ": resources=" + required); } intentService.submit(connectivityIntent); // Save circuit to connectivity intent mapping intentSetMultimap.allocateMapping(connectivityIntent.id(), intent.id()); FlowRuleIntent circuitIntent = createFlowRule(intent, connectivityIntent, required.stream(). flatMap(x -> Tools.stream(x.valueAs(TributarySlot.class))) .collect(Collectors.toSet())); return ImmutableList.of(circuitIntent); } private List<Resource> availableSlotResources(ConnectPoint src, ConnectPoint dst, CltSignalType signalType) { OduSignalType oduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(signalType); int requestedTsNum = oduSignalType.tributarySlots(); Set<TributarySlot> commonTributarySlots = findCommonTributarySlotsOnCps(src, dst); if (commonTributarySlots.isEmpty()) { return Collections.emptyList(); } if (commonTributarySlots.size() < requestedTsNum) { return Collections.emptyList(); } Set<TributarySlot> tributarySlots = commonTributarySlots.stream() .limit(requestedTsNum) .collect(Collectors.toSet()); final List<ConnectPoint> portsList = ImmutableList.of(src, dst); List<Resource> tributarySlotResources = portsList.stream() .flatMap(cp -> tributarySlots .stream() .map(ts -> Resources.discrete(cp.deviceId(), cp.port()).resource().child(ts))) .collect(Collectors.toList()); if (!tributarySlotResources.stream().allMatch(resourceService::isAvailable)) { log.debug("Resource allocation for {} on {} and {} failed (resource request: {})", signalType, src, dst, tributarySlotResources); return Collections.emptyList(); } return tributarySlotResources; } private FlowRuleIntent createFlowRule(OpticalCircuitIntent higherIntent, OpticalConnectivityIntent lowerIntent, Set<TributarySlot> slots) { // Create optical circuit intent List<FlowRule> rules = new LinkedList<>(); // at the source: ODUCLT port mapping to OCH port rules.add(connectPorts(higherIntent.getSrc(), lowerIntent.getSrc(), higherIntent.priority(), slots)); // at the destination: OCH port mapping to ODUCLT port rules.add(connectPorts(lowerIntent.getDst(), higherIntent.getDst(), higherIntent.priority(), slots)); // Create flow rules for reverse path if (higherIntent.isBidirectional()) { // at the destination: OCH port mapping to ODUCLT port rules.add(connectPorts(lowerIntent.getSrc(), higherIntent.getSrc(), higherIntent.priority(), slots)); // at the source: ODUCLT port mapping to OCH port rules.add(connectPorts(higherIntent.getDst(), lowerIntent.getDst(), higherIntent.priority(), slots)); } return new FlowRuleIntent(appId, higherIntent.key(), rules, higherIntent.resources(), PathIntent.ProtectionType.PRIMARY, higherIntent.resourceGroup()); } /** * Returns existing and available optical connectivity intent that matches the given circuit intent. * * @param src source connect point of optical circuit intent * @param dst destination connect point of optical circuit intent * @param signalType signal type of optical circuit intent * @param multiplexingSupported indicates whether ODU multiplexing is supported * @return existing optical connectivity intent, null otherwise. */ private OpticalConnectivityIntent findOpticalConnectivityIntent(ConnectPoint src, ConnectPoint dst, CltSignalType signalType, boolean multiplexingSupported) { OduSignalType oduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(signalType); return Tools.stream(intentService.getIntents()) .filter(x -> x instanceof OpticalConnectivityIntent) .map(x -> (OpticalConnectivityIntent) x) .filter(x -> src.deviceId().equals(x.getSrc().deviceId())) .filter(x -> dst.deviceId().equals(x.getDst().deviceId())) .filter(x -> isAllowed(src, x.getSrc())) .filter(x -> isAllowed(dst, x.getDst())) .filter(x -> isAvailable(x.id())) .filter(x -> !multiplexingSupported || isAvailableTributarySlots(x.getSrc(), x.getDst(), oduSignalType.tributarySlots())) .findFirst() .orElse(null); } private boolean isAllowed(ConnectPoint circuitCp, ConnectPoint connectivityCp) { ConnectPoint staticPort = staticPort(circuitCp); return staticPort == null || staticPort.equals(connectivityCp); } /** * Checks if current allocations on given resource can satisfy request. * If the resource is null, return true. * * @param resource the resource on which to map the intent * @return true if the resource can accept the request, false otherwise */ private boolean isAvailable(IntentId resource) { if (resource == null) { return true; } Set<IntentId> mapping = intentSetMultimap.getMapping(resource); if (mapping == null) { return true; } return mapping.size() < maxCapacity; } private boolean isAvailableTributarySlots(ConnectPoint src, ConnectPoint dst, int requestedTsNum) { Set<TributarySlot> common = findCommonTributarySlotsOnCps(src, dst); if (common.isEmpty()) { log.debug("No available TributarySlots"); return false; } if (common.size() < requestedTsNum) { log.debug("Not enough available TributarySlots={} < requestedTsNum={}", common.size(), requestedTsNum); return false; } return true; } private ConnectPoint staticPort(ConnectPoint connectPoint) { Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port()); String staticPort = port.annotations().value(AnnotationKeys.STATIC_PORT); // FIXME: need a better way to match the port if (staticPort != null) { for (Port p : deviceService.getPorts(connectPoint.deviceId())) { if (staticPort.equals(p.number().name())) { return new ConnectPoint(p.element().id(), p.number()); } } } return null; } private Pair<OchPort, OchPort> findPorts(ConnectPoint src, ConnectPoint dst, CltSignalType signalType) { // According to the OpticalCircuitIntent's signalType find OCH ports with available TributarySlots resources return Arrays.asList(OduSignalType.values()).stream() .sorted(Comparator.comparingLong(OduSignalType::bitRate)) .filter(oduSignalType -> oduSignalType.bitRate() >= signalType.bitRate()) .map(oduSignalType -> findPorts(src, dst, oduSignalType)) .flatMap(Tools::stream) .findFirst() .orElse(null); } private Optional<Pair<OchPort, OchPort>> findPorts(ConnectPoint src, ConnectPoint dst, OduSignalType ochPortSignalType) { return findAvailableOchPort(src, ochPortSignalType) .flatMap(srcOch -> findAvailableOchPort(dst, ochPortSignalType).map(dstOch -> Pair.of(srcOch, dstOch))); } private Optional<OchPort> findAvailableOchPort(ConnectPoint oduPort, OduSignalType ochPortSignalType) { // First see if the port mappings are constrained ConnectPoint ochCP = staticPort(oduPort); if (ochCP != null) { OchPort ochPort = (OchPort) deviceService.getPort(ochCP.deviceId(), ochCP.port()); Optional<IntentId> intentId = resourceService.getResourceAllocations(Resources.discrete(ochCP.deviceId(), ochCP.port()).id()) .stream() .map(ResourceAllocation::consumerId) .map(ResourceHelper::getIntentId) .flatMap(Tools::stream) .findAny(); if (isAvailable(intentId.orElse(null))) { return Optional.of(ochPort); } return Optional.empty(); } // No port constraints, so find any port that works List<Port> ports = deviceService.getPorts(oduPort.deviceId()); for (Port port : ports) { if (!(port instanceof OchPort)) { continue; } // This should be the first allocation on the OCH port if (!resourceService.isAvailable(Resources.discrete(oduPort.deviceId(), port.number()).resource())) { continue; } // OchPort is required to have the requested oduSignalType if (((OchPort) port).signalType() != ochPortSignalType) { continue; } Optional<IntentId> intentId = resourceService.getResourceAllocations(Resources.discrete(oduPort.deviceId(), port.number()).id()) .stream() .map(ResourceAllocation::consumerId) .map(ResourceHelper::getIntentId) .flatMap(Tools::stream) .findAny(); if (isAvailable(intentId.orElse(null))) { return Optional.of((OchPort) port); } } return Optional.empty(); } /** * Builds flow rule for mapping between two ports. * * @param src source port * @param dst destination port * @param priority * @param slots Set of TributarySlots * @return flow rules */ private FlowRule connectPorts(ConnectPoint src, ConnectPoint dst, int priority, Set<TributarySlot> slots) { checkArgument(src.deviceId().equals(dst.deviceId())); TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); selectorBuilder.matchInPort(src.port()); if (!slots.isEmpty()) { Port srcPort = deviceService.getPort(src.deviceId(), src.port()); Port dstPort = deviceService.getPort(dst.deviceId(), dst.port()); OduSignalType oduCltPortOduSignalType; OduSignalType ochPortOduSignalType; if (srcPort instanceof OduCltPort) { oduCltPortOduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(((OduCltPort) srcPort).signalType()); ochPortOduSignalType = ((OchPort) dstPort).signalType(); selectorBuilder.add(Criteria.matchOduSignalType(oduCltPortOduSignalType)); // use Instruction of OduSignalId only in case of ODU Multiplexing if (oduCltPortOduSignalType != ochPortOduSignalType) { OduSignalId oduSignalId = OduSignalUtils.buildOduSignalId(ochPortOduSignalType, slots); treatmentBuilder.add(Instructions.modL1OduSignalId(oduSignalId)); } } else { // srcPort is OchPort oduCltPortOduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(((OduCltPort) dstPort).signalType()); ochPortOduSignalType = ((OchPort) srcPort).signalType(); selectorBuilder.add(Criteria.matchOduSignalType(oduCltPortOduSignalType)); // use Criteria of OduSignalId only in case of ODU Multiplexing if (oduCltPortOduSignalType != ochPortOduSignalType) { OduSignalId oduSignalId = OduSignalUtils.buildOduSignalId(ochPortOduSignalType, slots); selectorBuilder.add(Criteria.matchOduSignalId(oduSignalId)); } } } treatmentBuilder.setOutput(dst.port()); FlowRule flowRule = DefaultFlowRule.builder() .forDevice(src.deviceId()) .withSelector(selectorBuilder.build()) .withTreatment(treatmentBuilder.build()) .withPriority(priority) .fromApp(appId) .makePermanent() .build(); return flowRule; } private boolean isMultiplexingSupported(ConnectPoint cp) { Driver driver = driverService.getDriver(cp.deviceId()); return driver != null && driver.hasBehaviour(TributarySlotQuery.class) && staticPort(cp) == null; } /** * Finds the common TributarySlots available on the two connect points. * * @param src source connect point * @param dst dest connect point * @return set of common TributarySlots on both connect points */ Set<TributarySlot> findCommonTributarySlotsOnCps(ConnectPoint src, ConnectPoint dst) { Set<TributarySlot> forward = findTributarySlotsOnCp(src); Set<TributarySlot> backward = findTributarySlotsOnCp(dst); return Sets.intersection(forward, backward); } /** * Finds the TributarySlots available on the connect point. * * @param cp connect point * @return set of TributarySlots available on the connect point */ Set<TributarySlot> findTributarySlotsOnCp(ConnectPoint cp) { return resourceService.getAvailableResourceValues( Resources.discrete(cp.deviceId(), cp.port()).id(), TributarySlot.class); } }