/* * 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 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.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; import org.onosproject.net.ConnectPoint; import org.onosproject.net.DeviceId; import org.onosproject.net.Link; import org.onosproject.net.LinkKey; import org.onosproject.net.OduSignalId; import org.onosproject.net.OduSignalType; import org.onosproject.net.OduSignalUtils; import org.onosproject.net.Path; import org.onosproject.net.Port; import org.onosproject.net.TributarySlot; import org.onosproject.net.device.DeviceService; 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.OpticalOduIntent; import org.onosproject.net.intent.PathIntent; import org.onosproject.net.optical.OduCltPort; import org.onosproject.net.optical.OtuPort; import org.onosproject.net.resource.Resource; import org.onosproject.net.resource.ResourceService; import org.onosproject.net.resource.ResourceAllocation; import org.onosproject.net.resource.Resources; import org.onosproject.net.topology.LinkWeight; import org.onosproject.net.topology.Topology; import org.onosproject.net.topology.TopologyService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; 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.LinkKey.linkKey; import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView; /** * An intent compiler for {@link org.onosproject.net.intent.OpticalOduIntent}. */ @Component(immediate = true) public class OpticalOduIntentCompiler implements IntentCompiler<OpticalOduIntent> { private static final Logger log = LoggerFactory.getLogger(OpticalOduIntentCompiler.class); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentExtensionService intentManager; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected TopologyService topologyService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DeviceService deviceService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ResourceService resourceService; private ApplicationId appId; @Activate public void activate() { deviceService = opticalView(deviceService); appId = coreService.registerApplication("org.onosproject.net.intent"); intentManager.registerCompiler(OpticalOduIntent.class, this); } @Deactivate public void deactivate() { intentManager.unregisterCompiler(OpticalOduIntent.class); } @Override public List<Intent> compile(OpticalOduIntent 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 ODU 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> intentResources = new ArrayList<>(); intentResources.add(srcPortResource); intentResources.add(dstPortResource); // Calculate available light paths Set<Path> paths = getOpticalPaths(intent); if (paths.isEmpty()) { throw new OpticalIntentCompilationException("Unable to find suitable lightpath for intent " + intent); } // Use first path that can be successfully reserved for (Path path : paths) { // Find available Tributary Slots on both directions of path Map<LinkKey, Set<TributarySlot>> slotsMap = findAvailableTributarySlots(intent, path); if (slotsMap.isEmpty()) { continue; } List<Resource> tributarySlotResources = convertToResources(slotsMap); if (!tributarySlotResources.stream().allMatch(resourceService::isAvailable)) { continue; } intentResources.addAll(tributarySlotResources); allocateResources(intent, intentResources); List<FlowRule> rules = new LinkedList<>(); // Create rules for forward and reverse path rules = createRules(intent, intent.getSrc(), intent.getDst(), path, slotsMap, false); if (intent.isBidirectional()) { rules.addAll(createRules(intent, intent.getDst(), intent.getSrc(), path, slotsMap, true)); } return Collections.singletonList( new FlowRuleIntent(appId, intent.key(), rules, ImmutableSet.copyOf(path.links()), PathIntent.ProtectionType.PRIMARY, intent.resourceGroup())); } throw new OpticalIntentCompilationException("Unable to find suitable lightpath for intent " + intent); } /** * Find available TributarySlots across path. * * @param intent * @param path path in OTU topology * @return Map of Linkey and Set of available TributarySlots on its ports */ private Map<LinkKey, Set<TributarySlot>> findAvailableTributarySlots(OpticalOduIntent intent, Path path) { Set<LinkKey> linkRequest = Sets.newHashSetWithExpectedSize(path.links().size()); for (int i = 0; i < path.links().size(); i++) { LinkKey link = linkKey(path.links().get(i)); linkRequest.add(link); } return findTributarySlots(intent, linkRequest); } private List<Resource> convertToResources(Map<LinkKey, Set<TributarySlot>> slotsMap) { // Same TributarySlots are used for both directions Set<Resource> resources = slotsMap.entrySet().stream() .flatMap(x -> x.getValue() .stream() .flatMap(ts -> Stream.of( Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port()) .resource().child(ts), Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port()) .resource().child(ts)))) .collect(Collectors.toSet()); return (ImmutableList.copyOf(resources)); } private void allocateResources(Intent intent, List<Resource> resources) { // reserve all of required resources List<ResourceAllocation> allocations = resourceService.allocate(intent.key(), resources); if (allocations.isEmpty()) { log.info("Resource allocation for {} failed (resource request: {})", intent, resources); throw new OpticalIntentCompilationException("Unable to allocate resources: " + resources); } } private Map<LinkKey, Set<TributarySlot>> findTributarySlots(OpticalOduIntent intent, Set<LinkKey> links) { OduSignalType oduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(intent.getSignalType()); int requestedTsNum = oduSignalType.tributarySlots(); Map<LinkKey, Set<TributarySlot>> slotsMap = new HashMap<>(); for (LinkKey link : links) { Set<TributarySlot> common = findCommonTributarySlotsOnCps(link.src(), link.dst()); if (common.isEmpty() || (common.size() < requestedTsNum)) { log.debug("Failed to find TributarySlots on link {} requestedTsNum={}", link, requestedTsNum); return Collections.emptyMap(); // failed to find enough available TributarySlots on a link } slotsMap.put(link, common.stream() .limit(requestedTsNum) .collect(Collectors.toSet())); } return slotsMap; } /** * Calculates optical paths in OTU topology. * * @param intent optical ODU intent * @return set of paths in OTU topology */ private Set<Path> getOpticalPaths(OpticalOduIntent intent) { // Route in OTU topology Topology topology = topologyService.currentTopology(); LinkWeight weight = edge -> { // Disregard inactive or non-optical links if (edge.link().state() == Link.State.INACTIVE) { return -1; } if (edge.link().type() != Link.Type.OPTICAL) { return -1; } // Find path with available TributarySlots resources if (!isAvailableTributarySlots(intent, edge.link())) { return -1; } return 1; }; ConnectPoint start = intent.getSrc(); ConnectPoint end = intent.getDst(); return topologyService.getPaths(topology, start.deviceId(), end.deviceId(), weight); } private boolean isAvailableTributarySlots(OpticalOduIntent intent, Link link) { OduSignalType oduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(intent.getSignalType()); int requestedTsNum = oduSignalType.tributarySlots(); Set<TributarySlot> common = findCommonTributarySlotsOnCps(link.src(), link.dst()); if (common.isEmpty() || (common.size() < requestedTsNum)) { log.debug("Not enough available TributarySlots on link {} requestedTsNum={}", link, requestedTsNum); return false; } return true; } /** * Create rules for the forward (or the reverse) path of the intent. * * @param intent OpticalOduIntent intent * @param path path found for intent * @param slotsMap Map of LinkKey and TributarySlots resources * @return list of flow rules */ private List<FlowRule> createRules(OpticalOduIntent intent, ConnectPoint src, ConnectPoint dst, Path path, Map<LinkKey, Set<TributarySlot>> slotsMap, boolean reverse) { // Build the ingress OTN rule TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); selector.matchInPort(src.port()); OduSignalType oduCltPortOduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(intent.getSignalType()); selector.add(Criteria.matchOduSignalType(oduCltPortOduSignalType)); List<FlowRule> rules = new LinkedList<>(); ConnectPoint current = src; List<Link> links = ((!reverse) ? path.links() : Lists.reverse(path.links())); for (Link link : links) { Set<TributarySlot> slots = slotsMap.get(linkKey(link)); OtuPort otuPort = (OtuPort) (deviceService.getPort(link.src().deviceId(), link.src().port())); OduSignalType otuPortOduSignalType = OduSignalUtils.mappingOtuSignalTypeToOduSignalType(otuPort.signalType()); TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(); OduSignalId oduSignalId = null; // use Instruction of OduSignalId only in case of ODU Multiplexing if (oduCltPortOduSignalType != otuPortOduSignalType) { oduSignalId = OduSignalUtils.buildOduSignalId(otuPortOduSignalType, slots); treat.add(Instructions.modL1OduSignalId(oduSignalId)); } ConnectPoint next = ((!reverse) ? link.src() : link.dst()); treat.setOutput(next.port()); FlowRule rule = createFlowRule(intent, current.deviceId(), selector.build(), treat.build()); rules.add(rule); current = ((!reverse) ? link.dst() : link.src()); selector = DefaultTrafficSelector.builder(); selector.matchInPort(current.port()); selector.add(Criteria.matchOduSignalType(oduCltPortOduSignalType)); // use Criteria of OduSignalId only in case of ODU Multiplexing if (oduCltPortOduSignalType != otuPortOduSignalType) { selector.add(Criteria.matchOduSignalId(oduSignalId)); } } // Build the egress OTN rule TrafficTreatment.Builder treatLast = DefaultTrafficTreatment.builder(); treatLast.setOutput(dst.port()); FlowRule rule = createFlowRule(intent, dst.deviceId(), selector.build(), treatLast.build()); rules.add(rule); return rules; } private FlowRule createFlowRule(OpticalOduIntent intent, DeviceId deviceId, TrafficSelector selector, TrafficTreatment treat) { return DefaultFlowRule.builder() .forDevice(deviceId) .withSelector(selector) .withTreatment(treat) .withPriority(intent.priority()) .fromApp(appId) .makePermanent() .build(); } /** * 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); } }