ONOS-5603 ProtectedTransportIntentCompiler
Change-Id: I681f24662d8e9be06f1e216fa9aa45b1dd44757d
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectedTransportEndpointDescription.java b/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectedTransportEndpointDescription.java
index 2e563de..dcdd895 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectedTransportEndpointDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectedTransportEndpointDescription.java
@@ -113,14 +113,29 @@
* Creates a {@link ProtectedTransportEndpointDescription}.
*
* @param paths {@link TransportEndpointDescription}s forming protection
- * @param did DeviceId of remote peer of this endpoint.
+ * @param peer DeviceId of remote peer of this endpoint.
+ * @param fingerprint opaque fingerprint object. must be serializable.
+ * @return {@link TransportEndpointDescription}
+ */
+ public static final ProtectedTransportEndpointDescription
+ buildDescription(List<TransportEndpointDescription> paths,
+ DeviceId peer,
+ String fingerprint) {
+ return new ProtectedTransportEndpointDescription(paths, peer, fingerprint);
+ }
+
+ /**
+ * Creates a {@link ProtectedTransportEndpointDescription}.
+ *
+ * @param paths {@link TransportEndpointDescription}s forming protection
+ * @param peer DeviceId of remote peer of this endpoint.
* @param fingerprint opaque fingerprint object. must be serializable.
* @return {@link TransportEndpointDescription}
*/
public static final ProtectedTransportEndpointDescription
of(List<TransportEndpointDescription> paths,
- DeviceId did,
+ DeviceId peer,
String fingerprint) {
- return new ProtectedTransportEndpointDescription(paths, did, fingerprint);
+ return new ProtectedTransportEndpointDescription(paths, peer, fingerprint);
}
}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectionConfig.java b/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectionConfig.java
index 1bcafba..085bc1f 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectionConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/protection/ProtectionConfig.java
@@ -16,6 +16,7 @@
package org.onosproject.net.behaviour.protection;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription.buildDescription;
import java.util.List;
@@ -127,7 +128,7 @@
* @return {@link ProtectedTransportEndpointDescription}
*/
public ProtectedTransportEndpointDescription asDescription() {
- return ProtectedTransportEndpointDescription.of(paths(), peer(), fingerprint());
+ return buildDescription(paths(), peer(), fingerprint());
}
@Override
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ProtectedTransportIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ProtectedTransportIntentCompiler.java
new file mode 100644
index 0000000..52c6dee
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ProtectedTransportIntentCompiler.java
@@ -0,0 +1,463 @@
+/*
+ * 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.intent.impl.compiler;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Lists.transform;
+import static java.util.stream.Stream.concat;
+import static org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription.buildDescription;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.RandomUtils;
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Link.State;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.Path;
+import org.onosproject.net.behaviour.protection.TransportEndpointDescription;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentCompilationException;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.intent.ProtectedTransportIntent;
+import org.onosproject.net.intent.ProtectionEndpointIntent;
+import org.onosproject.net.resource.DiscreteResourceId;
+import org.onosproject.net.resource.Resource;
+import org.onosproject.net.resource.ResourceService;
+import org.onosproject.net.resource.Resources;
+import org.slf4j.Logger;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+/**
+ * IntentCompiler for {@link ProtectedTransportIntent}.
+ */
+@Beta
+@Component(immediate = true)
+public class ProtectedTransportIntentCompiler
+ extends ConnectivityIntentCompiler<ProtectedTransportIntent> {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ResourceService resourceService;
+
+ @Activate
+ public void activate() {
+ intentManager.registerCompiler(ProtectedTransportIntent.class, this);
+ log.info("started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ intentManager.unregisterCompiler(ProtectedTransportIntent.class);
+ log.info("stopped");
+ }
+
+ @Override
+ public List<Intent> compile(ProtectedTransportIntent intent,
+ List<Intent> installable) {
+ log.trace("compiling {} {}", intent, installable);
+
+ // case 0 hop, same device
+ final DeviceId did1 = intent.one();
+ final DeviceId did2 = intent.two();
+ if (Objects.equals(did1, did2)) {
+ // Doesn't really make sense to create 0 hop protected path, but
+ // can generate Flow for the device, just to provide connectivity.
+ // future work.
+ log.error("0 hop not supported yet.");
+ throw new IntentCompilationException("0 hop not supported yet.");
+ }
+
+ List<Intent> reusable = Optional.ofNullable(installable).orElse(ImmutableList.of())
+ .stream()
+ .filter(this::isIntact)
+ .collect(Collectors.toList());
+ if (reusable.isEmpty() ||
+ reusable.stream().allMatch(ProtectionEndpointIntent.class::isInstance)) {
+ // case provisioning new protected path
+ // or
+ // case re-compilation (total failure -> restoration)
+ return createFreshProtectedPaths(intent, did1, did2);
+ } else {
+ // case re-compilation (partial failure)
+ log.warn("Re-computing adding new backup path not supported yet. No-Op.");
+ // TODO This part needs to be flexible to support various use case
+ // - non-revertive behavior (Similar to PartialFailureConstraint)
+ // - revertive behavior
+ // - compute third path
+ // ...
+ // Require further input what they actually need.
+
+ // TODO handle PartialFailureConstraint
+
+ /// case only need to update transit portion
+ /// case head and/or tail needs to be updated
+
+ // TODO do we need to prune broken
+ return installable;
+ }
+ }
+
+ /**
+ * Test if resources used by specified Intent is intact.
+ *
+ * @param installed Intent to test
+ * @return true if Intent is intact
+ */
+ private boolean isIntact(Intent installed) {
+ return installed.resources().stream()
+ .filter(Link.class::isInstance)
+ .map(Link.class::cast)
+ .allMatch(this::isLive);
+ }
+
+ /**
+ * Test if specified Link is intact.
+ *
+ * @param link to test
+ * @return true if link is intact
+ */
+ private boolean isLive(Link link) {
+ // Only testing link state for now
+ // in the long run, consider verifying OAM state on ports
+ return link.state() != State.INACTIVE;
+ }
+
+ /**
+ * Creates new protected paths.
+ *
+ * @param intent original intention
+ * @param did1 identifier of first device
+ * @param did2 identifier of second device
+ * @return compilation result
+ * @throws IntentCompilationException when there's no satisfying path.
+ */
+ private List<Intent> createFreshProtectedPaths(ProtectedTransportIntent intent,
+ DeviceId did1,
+ DeviceId did2) {
+ DisjointPath disjointPath = getDisjointPath(intent, did1, did2);
+ if (disjointPath == null || disjointPath.backup() == null) {
+ log.error("Unable to find disjoint path between {}, {}", did1, did2);
+ throw new IntentCompilationException("Unable to find disjoint paths.");
+ }
+ Path primary = disjointPath.primary();
+ Path secondary = disjointPath.backup();
+
+ String fingerprint = intent.key().toString();
+
+ // pick and allocate Vlan to use as S-tag
+ Pair<VlanId, VlanId> vlans = allocateEach(intent, primary, secondary, VlanId.class);
+
+ VlanId primaryVlan = vlans.getLeft();
+ VlanId secondaryVlan = vlans.getRight();
+
+ // Build edge Intents for head/tail
+
+ // resource for head/tail
+ Collection<NetworkResource> oneResources = new ArrayList<>();
+ Collection<NetworkResource> twoResources = new ArrayList<>();
+
+ List<TransportEndpointDescription> onePaths = new ArrayList<>();
+ onePaths.add(TransportEndpointDescription.builder()
+ .withOutput(vlanPort(primary.src(), primaryVlan))
+ .build());
+ onePaths.add(TransportEndpointDescription.builder()
+ .withOutput(vlanPort(secondary.src(), secondaryVlan))
+ .build());
+
+ List<TransportEndpointDescription> twoPaths = new ArrayList<>();
+ twoPaths.add(TransportEndpointDescription.builder()
+ .withOutput(vlanPort(primary.dst(), primaryVlan))
+ .build());
+ twoPaths.add(TransportEndpointDescription.builder()
+ .withOutput(vlanPort(secondary.dst(), secondaryVlan))
+ .build());
+
+ ProtectionEndpointIntent oneIntent = ProtectionEndpointIntent.builder()
+ .key(intent.key())
+ .appId(intent.appId())
+ .priority(intent.priority())
+ .resources(oneResources)
+ .deviceId(did1)
+ .description(buildDescription(onePaths, did2, fingerprint))
+ .build();
+ ProtectionEndpointIntent twoIntent = ProtectionEndpointIntent.builder()
+ .key(intent.key())
+ .appId(intent.appId())
+ .resources(twoResources)
+ .deviceId(did2)
+ .description(buildDescription(twoPaths, did1, fingerprint))
+ .build();
+
+ // Build transit intent for primary/secondary path
+
+ ImmutableList<Intent> result = ImmutableList.<Intent>builder()
+ // LinkCollection for primary and backup paths
+ .addAll(createTransitIntent(intent, primary, primaryVlan))
+ .addAll(createTransitIntent(intent, secondary, secondaryVlan))
+ .add(oneIntent)
+ .add(twoIntent)
+ .build();
+ log.trace("createFreshProtectedPaths result: {}", result);
+ return result;
+ }
+
+ /**
+ * Creates required Intents required to transit bi-directionally the network.
+ *
+ * @param intent parent IntentId
+ * @param path whole path
+ * @param vid VlanId to use as tunnel labels
+ * @return List on transit Intents, if any is required.
+ */
+ List<LinkCollectionIntent> createTransitIntent(Intent intent, Path path, VlanId vid) {
+ if (path.links().size() <= 1) {
+ // There's no need for transit Intents
+ return ImmutableList.of();
+ }
+
+ return ImmutableList.of(createSubTransitIntent(intent, path, vid),
+ createSubTransitIntent(intent, reverse(path), vid));
+ }
+
+ /**
+ * Returns a path in reverse direction.
+ *
+ * @param path to reverse
+ * @return reversed path
+ */
+ Path reverse(Path path) {
+ List<Link> revLinks = Lists.reverse(transform(path.links(), this::reverse));
+ return new DefaultPath(path.providerId(),
+ revLinks,
+ path.cost(),
+ path.annotations());
+ }
+
+ // TODO consider adding equivalent to Link/DefaultLink.
+ /**
+ * Returns a link in reverse direction.
+ *
+ * @param link to revese
+ * @return reversed link
+ */
+ Link reverse(Link link) {
+ return DefaultLink.builder()
+ .providerId(link.providerId())
+ .src(link.dst())
+ .dst(link.src())
+ .type(link.type())
+ .state(link.state())
+ .isExpected(link.isExpected())
+ .annotations(link.annotations())
+ .build();
+ }
+
+ /**
+ * Creates required Intents required to transit uni-directionally along the Path.
+ *
+ * @param intent parent IntentId
+ * @param path whole path
+ * @param vid VlanId to use as tunnel labels
+ * @return List on transit Intents, if any is required.
+ */
+ LinkCollectionIntent createSubTransitIntent(Intent intent, Path path, VlanId vid) {
+ checkArgument(path.links().size() > 1);
+
+ // transit ingress/egress
+ ConnectPoint one = path.links().get(0).dst();
+ ConnectPoint two = path.links().get(path.links().size() - 1).src();
+
+ return LinkCollectionIntent.builder()
+ // TODO there should probably be .parent(intent)
+ // which copies key, appId, priority, ...
+ .key(intent.key())
+ .appId(intent.appId())
+ .priority(intent.priority())
+ //.constraints(intent.constraints())
+ // VLAN tunnel
+ //.selector(DefaultTrafficSelector.builder().matchVlanId(vid).build())
+ //.treatment(intent.treatment())
+ .links(ImmutableSet.copyOf(path.links()))
+ .filteredIngressPoints(ImmutableSet.of(vlanPort(one, vid)))
+ .filteredEgressPoints(ImmutableSet.of(vlanPort(two, vid)))
+ // magic flag required for p2p type
+ .applyTreatmentOnEgress(true)
+ .cost(path.cost())
+ .build();
+ }
+
+ /**
+ * Creates VLAN filtered-ConnectPoint.
+ *
+ * @param cp ConnectPoint
+ * @param vid VLAN ID
+ * @return filtered-ConnectPoint
+ */
+ static FilteredConnectPoint vlanPort(ConnectPoint cp, VlanId vid) {
+ return new FilteredConnectPoint(cp, DefaultTrafficSelector.builder()
+ .matchVlanId(vid)
+ .build());
+ }
+
+ /**
+ * Creates ResourceId for a port.
+ *
+ * @param cp ConnectPoint
+ * @return ResourceId
+ */
+ static DiscreteResourceId resourceId(ConnectPoint cp) {
+ return Resources.discrete(cp.deviceId(), cp.port()).id();
+ }
+
+ /**
+ * Allocate resource for each {@link Path}s.
+ *
+ * @param intent to allocate resource to
+ * @param primary path
+ * @param secondary path
+ * @param klass label resource class
+ * @return Pair of chosen resource (primary, secondary)
+ * @param <T> label resource type
+ * @throws IntentCompilationException when there is no resource available
+ */
+ <T> Pair<T, T> allocateEach(Intent intent, Path primary, Path secondary, Class<T> klass) {
+ log.trace("allocateEach({}, {}, {}, {})", intent, primary, secondary, klass);
+ Pair<T, T> vlans = null;
+ do {
+ Set<T> primaryVlans = commonLabelResource(primary, klass);
+ Set<T> secondaryVlans = commonLabelResource(secondary, klass);
+ Pair<T, T> candidates = pickEach(primaryVlans, secondaryVlans);
+ T primaryT = candidates.getLeft();
+ T secondaryT = candidates.getRight();
+
+ // try to allocate candidates along each path
+ Stream<Resource> primaryResources = primary.links().stream()
+ .flatMap(link -> Stream.of(link.src(), link.dst()))
+ .distinct()
+ .map(cp -> Resources.discrete(resourceId(cp), primaryT).resource());
+ Stream<Resource> secondaryResources = secondary.links().stream()
+ .flatMap(link -> Stream.of(link.src(), link.dst()))
+ .distinct()
+ .map(cp -> Resources.discrete(resourceId(cp), secondaryT).resource());
+
+ List<Resource> resources = concat(primaryResources, secondaryResources)
+ .collect(Collectors.toList());
+ log.trace("Calling allocate({},{})", intent.id(), resources);
+ if (resourceService.allocate(intent.id(), resources).isEmpty()) {
+ log.warn("Allocation failed, retrying");
+ continue;
+ }
+ vlans = candidates;
+ } while (false);
+ log.trace("allocation done.");
+ return vlans;
+ }
+
+ /**
+ * Randomly pick one resource from candidates.
+ *
+ * @param set of candidates
+ * @return chosen one
+ * @param <T> label resource type
+ */
+ <T> T pickOne(Set<T> set) {
+ // Note: Set returned by commonLabelResource(..) assures,
+ // there is at least one element.
+
+ // FIXME more reasonable selection logic
+ return Iterables.get(set, RandomUtils.nextInt(0, set.size()));
+ }
+
+ /**
+ * Select resource from available Resources.
+ *
+ * @param primary Set of resource to pick from
+ * @param secondary Set of resource to pick from
+ * @return Pair of chosen resource (primary, secondary)
+ * @param <T> label resource type
+ */
+ <T> Pair<T, T> pickEach(Set<T> primary, Set<T> secondary) {
+ Set<T> intersection = Sets.intersection(primary, secondary);
+
+ if (!intersection.isEmpty()) {
+ // favor common
+ T picked = pickOne(intersection);
+ return Pair.of(picked, picked);
+ }
+
+ T pickedP = pickOne(primary);
+ T pickedS = pickOne(secondary);
+ return Pair.of(pickedP, pickedS);
+ }
+
+ /**
+ * Finds label resource, which can be used in common along the path.
+ *
+ * @param path path
+ * @param klass Label class
+ * @return Set of common resources
+ * @throws IntentCompilationException when there is no resource available
+ * @param <T> label resource type
+ */
+ <T> Set<T> commonLabelResource(Path path, Class<T> klass) {
+ Optional<Set<T>> common = path.links().stream()
+ .flatMap(link -> Stream.of(link.src(), link.dst()))
+ .distinct()
+ .map(cp -> getAvailableResourceValues(cp, klass))
+ .reduce(Sets::intersection);
+
+ if (!common.isPresent() || common.get().isEmpty()) {
+ throw new IntentCompilationException("No common label available for: " + path);
+ }
+ return common.get();
+ }
+
+ <T> Set<T> getAvailableResourceValues(ConnectPoint cp, Class<T> klass) {
+ return resourceService.getAvailableResourceValues(
+ resourceId(cp),
+ klass);
+ }
+
+}