| /* |
| * Copyright 2015-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.intent.impl.compiler; |
| |
| import com.google.common.collect.FluentIterable; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.felix.scr.annotations.ReferenceCardinality; |
| import org.onlab.graph.DefaultEdgeWeigher; |
| import org.onlab.graph.ScalarWeight; |
| import org.onlab.graph.Weight; |
| import org.onlab.util.Bandwidth; |
| import org.onosproject.net.ConnectPoint; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.DisjointPath; |
| import org.onosproject.net.ElementId; |
| import org.onosproject.net.Path; |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.intent.ConnectivityIntent; |
| import org.onosproject.net.intent.Constraint; |
| import org.onosproject.net.intent.IntentCompiler; |
| import org.onosproject.net.intent.IntentExtensionService; |
| import org.onosproject.net.intent.constraint.BandwidthConstraint; |
| import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint; |
| import org.onosproject.net.intent.constraint.MarkerConstraint; |
| import org.onosproject.net.intent.constraint.PathViabilityConstraint; |
| import org.onosproject.net.intent.impl.PathNotFoundException; |
| import org.onosproject.net.provider.ProviderId; |
| import org.onosproject.net.resource.Resource; |
| import org.onosproject.net.resource.ResourceAllocation; |
| import org.onosproject.net.resource.ResourceConsumer; |
| import org.onosproject.net.resource.ResourceId; |
| import org.onosproject.net.resource.ResourceService; |
| import org.onosproject.net.resource.Resources; |
| import org.onosproject.net.topology.LinkWeigher; |
| import org.onosproject.net.topology.PathService; |
| import org.onosproject.net.topology.TopologyEdge; |
| import org.onosproject.net.topology.TopologyVertex; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Base class for compilers of various |
| * {@link org.onosproject.net.intent.ConnectivityIntent connectivity intents}. |
| */ |
| @Component(immediate = true) |
| public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent> |
| implements IntentCompiler<T> { |
| |
| private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true); |
| |
| private static final Logger log = LoggerFactory.getLogger(ConnectivityIntentCompiler.class); |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected DeviceService deviceService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected IntentExtensionService intentManager; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected PathService pathService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected ResourceService resourceService; |
| |
| /** |
| * Returns an edge-weight capable of evaluating links on the basis of the |
| * specified constraints. |
| * |
| * @param constraints path constraints |
| * @return edge-weight function |
| */ |
| protected LinkWeigher weigher(List<Constraint> constraints) { |
| return new ConstraintBasedLinkWeigher(constraints); |
| } |
| |
| /** |
| * Validates the specified path against the given constraints. |
| * |
| * @param path path to be checked |
| * @param constraints path constraints |
| * @return true if the path passes all constraints |
| */ |
| protected boolean checkPath(Path path, List<Constraint> constraints) { |
| if (path == null) { |
| return false; |
| } |
| for (Constraint constraint : constraints) { |
| if (!constraint.validate(path, resourceService::isAvailable)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Computes a path between two ConnectPoints. |
| * |
| * @param intent intent on which behalf path is being computed |
| * @param one start of the path |
| * @param two end of the path |
| * @return Path between the two |
| * @throws PathNotFoundException if a path cannot be found |
| */ |
| @Deprecated |
| protected Path getPathOrException(ConnectivityIntent intent, |
| ElementId one, ElementId two) { |
| Path path = getPath(intent, one, two); |
| if (path == null) { |
| throw new PathNotFoundException(one, two); |
| } |
| // TODO: let's be more intelligent about this eventually |
| return path; |
| } |
| |
| /** |
| * Computes a path between two ConnectPoints. |
| * |
| * @param intent intent on which behalf path is being computed |
| * @param one start of the path |
| * @param two end of the path |
| * @return Path between the two, or null if no path can be found |
| */ |
| protected Path getPath(ConnectivityIntent intent, |
| ElementId one, ElementId two) { |
| Set<Path> paths = pathService.getPaths(one, two, weigher(intent.constraints())); |
| final List<Constraint> constraints = intent.constraints(); |
| ImmutableList<Path> filtered = FluentIterable.from(paths) |
| .filter(path -> checkPath(path, constraints)) |
| .toList(); |
| if (filtered.isEmpty()) { |
| return null; |
| } |
| |
| if (constraints.stream().anyMatch(c -> c instanceof HashedPathSelectionConstraint)) { |
| return filtered.get(intent.hashCode() % filtered.size()); |
| } |
| |
| return filtered.iterator().next(); |
| } |
| |
| /** |
| * Computes a disjoint path between two ConnectPoints. |
| * |
| * @param intent intent on which behalf path is being computed |
| * @param one start of the path |
| * @param two end of the path |
| * @return DisjointPath between the two |
| * @throws PathNotFoundException if two paths cannot be found |
| */ |
| protected DisjointPath getDisjointPath(ConnectivityIntent intent, |
| ElementId one, ElementId two) { |
| Set<DisjointPath> paths = pathService.getDisjointPaths(one, two, weigher(intent.constraints())); |
| final List<Constraint> constraints = intent.constraints(); |
| ImmutableList<DisjointPath> filtered = FluentIterable.from(paths) |
| .filter(path -> checkPath(path, constraints)) |
| .filter(path -> checkPath(path.backup(), constraints)) |
| .toList(); |
| if (filtered.isEmpty()) { |
| throw new PathNotFoundException(one, two); |
| } |
| |
| if (constraints.stream().anyMatch(c -> c instanceof HashedPathSelectionConstraint)) { |
| return filtered.get(intent.hashCode() % filtered.size()); |
| } |
| |
| return filtered.iterator().next(); |
| } |
| |
| /** |
| * Allocates the bandwidth specified as intent constraint on each link |
| * composing the intent, if a bandwidth constraint is specified. |
| * |
| * @param intent the intent requesting bandwidth allocation |
| * @param connectPoints the connect points composing the intent path computed |
| */ |
| protected void allocateBandwidth(ConnectivityIntent intent, |
| List<ConnectPoint> connectPoints) { |
| // Retrieve bandwidth constraint if exists |
| List<Constraint> constraints = intent.constraints(); |
| |
| if (constraints == null) { |
| return; |
| } |
| |
| Optional<Constraint> constraint = |
| constraints.stream() |
| .filter(c -> c instanceof BandwidthConstraint) |
| .findAny(); |
| |
| // If there is no bandwidth constraint continue |
| if (!constraint.isPresent()) { |
| return; |
| } |
| |
| BandwidthConstraint bwConstraint = (BandwidthConstraint) constraint.get(); |
| |
| double bw = bwConstraint.bandwidth().bps(); |
| |
| // If a resource group is set on the intent, the resource consumer is |
| // set equal to it. Otherwise it's set to the intent key |
| ResourceConsumer newResourceConsumer = |
| intent.resourceGroup() != null ? intent.resourceGroup() : intent.key(); |
| |
| // Get the list of current resource allocations |
| Collection<ResourceAllocation> resourceAllocations = |
| resourceService.getResourceAllocations(newResourceConsumer); |
| |
| // Get the list of resources already allocated from resource allocations |
| List<Resource> resourcesAllocated = |
| resourcesFromAllocations(resourceAllocations); |
| |
| // Get the list of resource ids for resources already allocated |
| List<ResourceId> idsResourcesAllocated = resourceIds(resourcesAllocated); |
| |
| // Create the list of incoming resources requested. Exclude resources |
| // already allocated. |
| List<Resource> incomingResources = |
| resources(connectPoints, bw).stream() |
| .filter(r -> !resourcesAllocated.contains(r)) |
| .collect(Collectors.toList()); |
| |
| if (incomingResources.isEmpty()) { |
| return; |
| } |
| |
| // Create the list of resources to be added, meaning their key is not |
| // present in the resources already allocated |
| List<Resource> resourcesToAdd = |
| incomingResources.stream() |
| .filter(r -> !idsResourcesAllocated.contains(r.id())) |
| .collect(Collectors.toList()); |
| |
| // Resources to updated are all the new valid resources except the |
| // resources to be added |
| List<Resource> resourcesToUpdate = Lists.newArrayList(incomingResources); |
| resourcesToUpdate.removeAll(resourcesToAdd); |
| |
| // If there are no resources to update skip update procedures |
| if (!resourcesToUpdate.isEmpty()) { |
| // Remove old resources that need to be updated |
| // TODO: use transaction updates when available in the resource service |
| List<ResourceAllocation> resourceAllocationsToUpdate = |
| resourceAllocations.stream() |
| .filter(rA -> resourceIds(resourcesToUpdate).contains(rA.resource().id())) |
| .collect(Collectors.toList()); |
| log.debug("Releasing bandwidth for intent {}: {} bps", newResourceConsumer, resourcesToUpdate); |
| resourceService.release(resourceAllocationsToUpdate); |
| |
| // Update resourcesToAdd with the list of both the new resources and |
| // the resources to update |
| resourcesToAdd.addAll(resourcesToUpdate); |
| } |
| |
| // Look also for resources allocated using the intent key and -if any- |
| // remove them |
| if (intent.resourceGroup() != null) { |
| // Get the list of current resource allocations made by intent key |
| Collection<ResourceAllocation> resourceAllocationsByKey = |
| resourceService.getResourceAllocations(intent.key()); |
| |
| resourceService.release(Lists.newArrayList(resourceAllocationsByKey)); |
| } |
| |
| // Allocate resources |
| log.debug("Allocating bandwidth for intent {}: {} bps", newResourceConsumer, resourcesToAdd); |
| List<ResourceAllocation> allocations = |
| resourceService.allocate(newResourceConsumer, resourcesToAdd); |
| |
| if (allocations.isEmpty()) { |
| log.debug("No resources allocated for intent {}", newResourceConsumer); |
| } |
| |
| log.debug("Done allocating bandwidth for intent {}", newResourceConsumer); |
| } |
| |
| /** |
| * Produces a list of resources from a list of resource allocations. |
| * |
| * @param rAs the list of resource allocations |
| * @return a list of resources retrieved from the resource allocations given |
| */ |
| private static List<Resource> resourcesFromAllocations(Collection<ResourceAllocation> rAs) { |
| return rAs.stream() |
| .map(ResourceAllocation::resource) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Creates a list of continuous bandwidth resources given a list of connect |
| * points and a bandwidth. |
| * |
| * @param cps the list of connect points |
| * @param bw the bandwidth expressed as a double |
| * @return the list of resources |
| */ |
| private static List<Resource> resources(List<ConnectPoint> cps, double bw) { |
| return cps.stream() |
| // Make sure the element id is a valid device id |
| .filter(cp -> cp.elementId() instanceof DeviceId) |
| // Create a continuous resource for each CP we're going through |
| .map(cp -> Resources.continuous(cp.deviceId(), cp.port(), |
| Bandwidth.class).resource(bw)) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Returns a list of resource ids given a list of resources. |
| * |
| * @param resources the list of resources |
| * @return the list of resource ids retrieved from the resources given |
| */ |
| private static List<ResourceId> resourceIds(List<Resource> resources) { |
| return resources.stream() |
| .map(Resource::id) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Edge-weight capable of evaluating link cost using a set of constraints. |
| */ |
| protected class ConstraintBasedLinkWeigher extends DefaultEdgeWeigher<TopologyVertex, TopologyEdge> |
| implements LinkWeigher { |
| |
| private final List<Constraint> constraints; |
| |
| /** |
| * Creates a new edge-weight function capable of evaluating links |
| * on the basis of the specified constraints. |
| * |
| * @param constraints path constraints |
| */ |
| ConstraintBasedLinkWeigher(List<Constraint> constraints) { |
| if (constraints == null) { |
| this.constraints = Collections.emptyList(); |
| } else { |
| this.constraints = ImmutableList.copyOf(constraints); |
| } |
| } |
| |
| @Override |
| public Weight weight(TopologyEdge edge) { |
| |
| // iterate over all constraints in order and return the weight of |
| // the first one with fast fail over the first failure |
| Iterator<Constraint> it = constraints.stream() |
| .filter(c -> !(c instanceof MarkerConstraint)) |
| .filter(c -> !(c instanceof PathViabilityConstraint)) |
| .iterator(); |
| |
| if (!it.hasNext()) { |
| return DEFAULT_HOP_WEIGHT; |
| } |
| |
| double cost = it.next().cost(edge.link(), resourceService::isAvailable); |
| while (it.hasNext() && cost > 0) { |
| if (it.next().cost(edge.link(), resourceService::isAvailable) < 0) { |
| // TODO shouldn't this be non-viable? |
| cost = -1; |
| } |
| } |
| return ScalarWeight.toWeight(cost); |
| |
| } |
| } |
| |
| } |