Implement path protection for point to point intents
Change-Id: I3f3627e7c2a7e3ab017e46655692ab70fdeae413
diff --git a/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java b/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java
index 9bfd199..14db547 100644
--- a/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java
@@ -17,6 +17,7 @@
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
@@ -24,6 +25,7 @@
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.ProtectionConstraint;
import java.util.List;
@@ -44,6 +46,11 @@
required = true, multiValued = false)
String egressDeviceString = null;
+ @Option(name = "-p", aliases = "--protect",
+ description = "Utilize path protection",
+ required = false, multiValued = false)
+ private boolean backup = false;
+
@Override
protected void execute() {
IntentService service = get(IntentService.class);
@@ -56,6 +63,9 @@
TrafficTreatment treatment = buildTrafficTreatment();
List<Constraint> constraints = buildConstraints();
+ if (backup) {
+ constraints.add(new ProtectionConstraint());
+ }
Intent intent = PointToPointIntent.builder()
.appId(appId())
diff --git a/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java b/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
index df90c41..921c279 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
@@ -35,6 +35,7 @@
public class FlowRuleIntent extends Intent {
private final Collection<FlowRule> flowRules;
+ private PathIntent.ProtectionType type;
/**
* Creates a flow rule intent with the specified flow rules and resources.
@@ -48,7 +49,19 @@
}
/**
- * Creates an flow rule intent with the specified key, flow rules to be set, and
+ * Creates a flow rule intent with the specified flow rules, resources, and type.
+ *
+ * @param appId application id
+ * @param flowRules flow rules to be set
+ * @param resources network resource to be set
+ */
+ public FlowRuleIntent(ApplicationId appId, List<FlowRule> flowRules, Collection<NetworkResource> resources,
+ PathIntent.ProtectionType type) {
+ this(appId, null, flowRules, resources, type);
+ }
+
+ /**
+ * Creates a flow rule intent with the specified key, flow rules to be set, and
* required network resources.
*
* @param appId application id
@@ -58,8 +71,32 @@
*/
public FlowRuleIntent(ApplicationId appId, Key key, Collection<FlowRule> flowRules,
Collection<NetworkResource> resources) {
+ this(appId, key, flowRules, resources, PathIntent.ProtectionType.PRIMARY);
+ }
+
+ /**
+ * Creates a flow rule intent with the specified key, flow rules to be set, and
+ * required network resources.
+ *
+ * @param appId application id
+ * @param key key
+ * @param flowRules flow rules
+ * @param resources network resources
+ */
+ public FlowRuleIntent(ApplicationId appId, Key key, Collection<FlowRule> flowRules,
+ Collection<NetworkResource> resources, PathIntent.ProtectionType primary) {
super(appId, key, resources, DEFAULT_INTENT_PRIORITY);
this.flowRules = ImmutableList.copyOf(checkNotNull(flowRules));
+ this.type = primary;
+ }
+
+ /**
+ * Creates a flow rule intent with all the same characteristics as the given
+ * one except for the flow rule type.
+ */
+ public FlowRuleIntent(FlowRuleIntent intent, PathIntent.ProtectionType type) {
+ this(intent.appId(), intent.key(), intent.flowRules(),
+ intent.resources(), type);
}
/**
@@ -68,6 +105,7 @@
protected FlowRuleIntent() {
super();
this.flowRules = null;
+ this.type = PathIntent.ProtectionType.PRIMARY;
}
/**
@@ -84,6 +122,10 @@
return true;
}
+ public PathIntent.ProtectionType type() {
+ return type;
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
diff --git a/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java b/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
index 63fff1d..f2a0e45 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
@@ -31,7 +31,7 @@
* Compiles the specified intent into other intents.
*
* @param intent intent to be compiled
- * @param installable previously compilation result; optional
+ * @param installable previous compilation result; optional
* @return list of resulting intents
* @throws IntentException if issues are encountered while compiling the intent
*/
diff --git a/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java b/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
index b674c06..7ac22b3 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
@@ -36,10 +36,11 @@
public class PathIntent extends ConnectivityIntent {
private final Path path;
+ private ProtectionType type;
/**
* Creates a new point-to-point intent with the supplied ingress/egress
- * ports and using the specified explicit path.
+ * ports and using the specified explicit path. Path is primary by default.
*
* @param appId application identifier
* @param key intent key
@@ -57,10 +58,38 @@
Path path,
List<Constraint> constraints,
int priority) {
+ this(appId, key, selector, treatment, path, constraints, priority,
+ ProtectionType.PRIMARY);
+ }
+
+ /**
+ * Creates a new point-to-point intent with the supplied ingress/egress
+ * ports and using the specified explicit path, which can be classified
+ * as PRIMARY or BACKUP.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ * @param selector traffic selector
+ * @param treatment treatment
+ * @param path traversed links
+ * @param constraints optional list of constraints
+ * @param priority priority to use for the generated flows
+ * @param type PRIMARY or BACKUP
+ * @throws NullPointerException {@code path} is null
+ */
+ protected PathIntent(ApplicationId appId,
+ Key key,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ Path path,
+ List<Constraint> constraints,
+ int priority,
+ ProtectionType type) {
super(appId, key, resources(path.links()), selector, treatment, constraints,
- priority);
+ priority);
PathIntent.validate(path.links());
this.path = path;
+ this.type = type;
}
/**
@@ -69,6 +98,7 @@
protected PathIntent() {
super();
this.path = null;
+ this.type = ProtectionType.PRIMARY;
}
/**
@@ -85,6 +115,7 @@
*/
public static class Builder extends ConnectivityIntent.Builder {
Path path;
+ ProtectionType type;
protected Builder() {
// Hide default constructor
@@ -131,6 +162,11 @@
return this;
}
+ public Builder setType(ProtectionType type) {
+ this.type = type;
+ return this;
+ }
+
/**
* Builds a path intent from the accumulated parameters.
*
@@ -145,7 +181,8 @@
treatment,
path,
constraints,
- priority
+ priority,
+ type == null ? ProtectionType.PRIMARY : type
);
}
}
@@ -183,6 +220,10 @@
return path;
}
+ public ProtectionType type() {
+ return type;
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
@@ -195,7 +236,25 @@
.add("treatment", treatment())
.add("constraints", constraints())
.add("path", path)
+ .add("type", type)
.toString();
}
+ // for path protection purposes
+ public enum ProtectionType {
+ /**
+ * Intent within primary path.
+ */
+ PRIMARY,
+ /**
+ * Intent within backup path.
+ */
+ BACKUP,
+ /**
+ * Intent whose flow rule serves as the fast failover
+ * between primary and backup paths.
+ */
+ FAILOVER
+ }
+
}
diff --git a/core/api/src/main/java/org/onosproject/net/intent/constraint/ProtectionConstraint.java b/core/api/src/main/java/org/onosproject/net/intent/constraint/ProtectionConstraint.java
new file mode 100644
index 0000000..d4d2c74
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/intent/constraint/ProtectionConstraint.java
@@ -0,0 +1,58 @@
+/*
+ * 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.constraint;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.ResourceContext;
+
+/**
+ * Constraint that determines whether to employ path protection.
+ */
+@Beta
+public class ProtectionConstraint implements Constraint {
+ // doesn't use LinkResourceService
+ @Override
+ public double cost(Link link, ResourceContext context) {
+ return 1;
+ }
+
+ // doesn't use LinkResourceService
+ @Override
+ public boolean validate(Path path, ResourceContext context) {
+ return true;
+ }
+
+ /**
+ * Determines whether to utilize path protection for the given intent.
+ *
+ * @param intent intent to be inspected
+ * @return whether the intent has a ProtectionConstraint
+ */
+ public static boolean requireProtectedPath(Intent intent) {
+ if (intent instanceof PointToPointIntent) {
+ PointToPointIntent pointToPointIntent = (PointToPointIntent) intent;
+ return pointToPointIntent.constraints().stream()
+ .anyMatch(p -> p instanceof ProtectionConstraint);
+ }
+ return false;
+ }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java b/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
index a87263a..d3db258 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
@@ -26,7 +26,7 @@
public class PointToPointIntentTest extends ConnectivityIntentTest {
/**
- * Checks that the MultiPointToSinglePointIntent class is immutable.
+ * Checks that the PointToPointIntent class is immutable.
*/
@Test
public void checkImmutability() {
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
index fc7e568..07759e3 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
@@ -28,8 +28,11 @@
import org.onosproject.core.CoreService;
import org.onosproject.core.IdGenerator;
import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.net.DeviceId;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentBatchDelegate;
import org.onosproject.net.intent.IntentCompiler;
@@ -42,6 +45,8 @@
import org.onosproject.net.intent.IntentStore;
import org.onosproject.net.intent.IntentStoreDelegate;
import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.impl.compiler.PointToPointIntentCompiler;
import org.onosproject.net.intent.impl.phase.FinalIntentProcessPhase;
import org.onosproject.net.intent.impl.phase.IntentProcessPhase;
import org.osgi.service.component.ComponentContext;
@@ -123,6 +128,9 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService configService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected GroupService groupService;
+
private ExecutorService batchExecutor;
private ExecutorService workerExecutor;
@@ -234,6 +242,15 @@
checkNotNull(intent, INTENT_NULL);
IntentData data = new IntentData(intent, IntentState.PURGE_REQ, null);
store.addPending(data);
+
+ // remove associated group if there is one
+ if (intent instanceof PointToPointIntent) {
+ PointToPointIntent pointIntent = (PointToPointIntent) intent;
+ DeviceId deviceId = pointIntent.ingressPoint().deviceId();
+ GroupKey groupKey = PointToPointIntentCompiler.makeGroupKey(intent.id());
+ groupService.removeGroup(deviceId, groupKey,
+ intent.appId());
+ }
}
@Override
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
index 4b51714..6dd4ebd 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
@@ -20,6 +20,7 @@
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.DisjointPath;
import org.onosproject.net.ElementId;
import org.onosproject.net.Path;
import org.onosproject.net.intent.ConnectivityIntent;
@@ -108,6 +109,29 @@
}
/**
+ * 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, weight(intent.constraints()));
+ final List<Constraint> constraints = intent.constraints();
+ ImmutableList<DisjointPath> filtered = FluentIterable.from(paths)
+ .filter(path -> checkPath(path, constraints))
+ .toList();
+ if (filtered.isEmpty()) {
+ throw new PathNotFoundException(one, two);
+ }
+ // TODO: let's be more intelligent about this eventually
+ return filtered.iterator().next();
+ }
+
+ /**
* Edge-weight capable of evaluating link cost using a set of constraints.
*/
protected class ConstraintBasedLinkWeight implements LinkWeight {
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java
index ff69f29..fdcdd00 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java
@@ -82,7 +82,7 @@
compile(this, intent, rules, devices);
- return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources()));
+ return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources(), intent.type()));
}
@Override
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
index 28d7ea5..0de641a 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
@@ -18,17 +18,51 @@
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.net.ConnectPoint;
import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.EdgeLink;
import org.onosproject.net.Link;
import org.onosproject.net.Path;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+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.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentId;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.ProtectionConstraint;
+import org.onosproject.net.intent.impl.PathNotFoundException;
+import org.onosproject.net.link.LinkService;
import org.onosproject.net.provider.ProviderId;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import static java.util.Arrays.asList;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
@@ -45,6 +79,18 @@
new ProviderId("core", "org.onosproject.core", true);
// TODO: consider whether the default cost is appropriate or not
public static final int DEFAULT_COST = 1;
+ protected static final int PRIORITY = Intent.DEFAULT_INTENT_PRIORITY;
+ protected boolean erasePrimary = false;
+ protected boolean eraseBackup = false;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected GroupService groupService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LinkService linkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
@Activate
public void activate() {
@@ -63,20 +109,160 @@
ConnectPoint egressPoint = intent.egressPoint();
if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
- List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false));
- return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST), intent));
+ return createZeroHopIntent(ingressPoint, egressPoint, intent);
}
+ // proceed with no protected paths
+ if (!ProtectionConstraint.requireProtectedPath(intent)) {
+ return createUnprotectedIntent(ingressPoint, egressPoint, intent);
+ }
+
+ try {
+ // attempt to compute and implement backup path
+ return createProtectedIntent(ingressPoint, egressPoint, intent, installable);
+ } catch (PathNotFoundException e) {
+ // no disjoint path extant -- maximum one path exists between devices
+ return createSinglePathIntent(ingressPoint, egressPoint, intent, installable);
+ }
+ }
+
+ private List<Intent> createZeroHopIntent(ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ PointToPointIntent intent) {
+ List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false));
+ return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST),
+ intent, PathIntent.ProtectionType.PRIMARY));
+ }
+
+ private List<Intent> createUnprotectedIntent(ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ PointToPointIntent intent) {
List<Link> links = new ArrayList<>();
Path path = getPath(intent, ingressPoint.deviceId(),
- egressPoint.deviceId());
+ egressPoint.deviceId());
links.add(createEdgeLink(ingressPoint, true));
links.addAll(path.links());
links.add(createEdgeLink(egressPoint, false));
return asList(createPathIntent(new DefaultPath(PID, links, path.cost(),
- path.annotations()), intent));
+ path.annotations()), intent,
+ PathIntent.ProtectionType.PRIMARY));
+ }
+
+ //FIXME: Compatibility with EncapsulationConstraint
+ private List<Intent> createProtectedIntent(ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ PointToPointIntent intent,
+ List<Intent> installable) {
+ DisjointPath path = getDisjointPath(intent, ingressPoint.deviceId(),
+ egressPoint.deviceId());
+
+ List<Intent> reusableIntents = null;
+ if (installable != null) {
+ reusableIntents = filterInvalidSubIntents(installable, intent);
+ if (reusableIntents.size() == installable.size()) {
+ // all old paths are still viable
+ return installable;
+ }
+ }
+
+ List<Intent> intentList = new ArrayList<>();
+
+ // primary path intent
+ List<Link> links = new ArrayList<>();
+ links.addAll(path.links());
+ links.add(createEdgeLink(egressPoint, false));
+
+ // backup path intent
+ List<Link> backupLinks = new ArrayList<>();
+ backupLinks.addAll(path.backup().links());
+ backupLinks.add(createEdgeLink(egressPoint, false));
+
+ /*
+ * One of the old paths is still entirely intact. This old path has
+ * already been made primary, so we must add a backup path intent
+ * and modify the failover group treatment accordingly.
+ */
+ if (reusableIntents != null && reusableIntents.size() > 1) {
+ /*
+ * Ensures that the egress port on source device is different than
+ * that of existing path so that failover group will be useful
+ * (would not be useful if both output ports in group bucket were
+ * the same). Does not necessarily ensure that the new backup path
+ * is entirely disjoint from the old path.
+ */
+ PortNumber primaryPort = getPrimaryPort(intent);
+ if (primaryPort != null && !links.get(0).src().port().equals(primaryPort)) {
+ reusableIntents.add(createPathIntent(new DefaultPath(PID, links,
+ path.cost(), path.annotations()),
+ intent, PathIntent.ProtectionType.BACKUP));
+ updateFailoverGroup(intent, links);
+ return reusableIntents;
+
+ } else {
+ reusableIntents.add(createPathIntent(new DefaultPath(PID, backupLinks, path.backup().cost(),
+ path.backup().annotations()), intent, PathIntent.ProtectionType.BACKUP));
+ updateFailoverGroup(intent, backupLinks);
+ return reusableIntents;
+ }
+ }
+
+ intentList.add(createPathIntent(new DefaultPath(PID, links, path.cost(), path.annotations()),
+ intent, PathIntent.ProtectionType.PRIMARY));
+ intentList.add(createPathIntent(new DefaultPath(PID, backupLinks, path.backup().cost(),
+ path.backup().annotations()), intent, PathIntent.ProtectionType.BACKUP));
+
+ // Create fast failover flow rule intent or, if it already exists,
+ // add contents appropriately.
+ if (groupService.getGroup(ingressPoint.deviceId(),
+ makeGroupKey(intent.id())) == null) {
+ // manufactured fast failover flow rule intent
+ createFailoverTreatmentGroup(path.links(), path.backup().links(), intent);
+
+ FlowRuleIntent frIntent = new FlowRuleIntent(intent.appId(),
+ createFailoverFlowRules(intent),
+ asList(ingressPoint.deviceId()),
+ PathIntent.ProtectionType.FAILOVER);
+ intentList.add(frIntent);
+ } else {
+ updateFailoverGroup(intent, links);
+ updateFailoverGroup(intent, backupLinks);
+ }
+
+ return intentList;
+ }
+
+ private List<Intent> createSinglePathIntent(ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ PointToPointIntent intent,
+ List<Intent> installable) {
+ List<Link> links = new ArrayList<>();
+ Path onlyPath = getPath(intent, ingressPoint.deviceId(),
+ egressPoint.deviceId());
+
+ List<Intent> reusableIntents = null;
+ if (installable != null) {
+ reusableIntents = filterInvalidSubIntents(installable, intent);
+ if (reusableIntents.size() == installable.size()) {
+ // all old paths are still viable
+ return installable;
+ }
+ }
+
+ // If there exists a full path from old installable intents,
+ // return the intents that comprise it.
+ if (reusableIntents != null && reusableIntents.size() > 1) {
+ return reusableIntents;
+ } else {
+ links.add(createEdgeLink(ingressPoint, true));
+ links.addAll(onlyPath.links());
+ links.add(createEdgeLink(egressPoint, false));
+
+ return asList(createPathIntent(new DefaultPath(PID, links, onlyPath.cost(),
+ onlyPath.annotations()),
+ intent, PathIntent.ProtectionType.PRIMARY));
+ }
}
/**
@@ -85,9 +271,11 @@
*
* @param path path to create an intent for
* @param intent original intent
+ * @param type primary or backup
*/
private Intent createPathIntent(Path path,
- PointToPointIntent intent) {
+ PointToPointIntent intent,
+ PathIntent.ProtectionType type) {
return PathIntent.builder()
.appId(intent.appId())
.selector(intent.selector())
@@ -95,7 +283,262 @@
.path(path)
.constraints(intent.constraints())
.priority(intent.priority())
+ .setType(type)
.build();
}
+ /**
+ * Gets primary port number through failover group associated
+ * with this intent.
+ */
+ private PortNumber getPrimaryPort(PointToPointIntent intent) {
+ Group group = groupService.getGroup(intent.ingressPoint().deviceId(),
+ makeGroupKey(intent.id()));
+ PortNumber primaryPort = null;
+ if (group != null) {
+ List<GroupBucket> buckets = group.buckets().buckets();
+ Iterator<GroupBucket> iterator = buckets.iterator();
+ while (primaryPort == null && iterator.hasNext()) {
+ GroupBucket bucket = iterator.next();
+ Instruction individualInstruction = bucket.treatment().allInstructions().get(0);
+ if (individualInstruction instanceof Instructions.OutputInstruction) {
+ Instructions.OutputInstruction outInstruction =
+ (Instructions.OutputInstruction) individualInstruction;
+ PortNumber tempPortNum = outInstruction.port();
+ Port port = deviceService.getPort(intent.ingressPoint().deviceId(),
+ tempPortNum);
+ if (port != null && port.isEnabled()) {
+ primaryPort = tempPortNum;
+ }
+ }
+ }
+ }
+ return primaryPort;
+ }
+
+ /**
+ * Creates group key unique to each intent.
+ */
+ public static GroupKey makeGroupKey(IntentId intentId) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.putLong(intentId.fingerprint());
+ return new DefaultGroupKey(buffer.array());
+ }
+
+ /**
+ * Creates a new failover group with the initial ports of the links
+ * from the primary and backup path.
+ *
+ * @param links links from the primary path
+ * @param backupLinks links from the backup path
+ * @param intent intent from which this call originates
+ */
+ private void createFailoverTreatmentGroup(List<Link> links,
+ List<Link> backupLinks,
+ PointToPointIntent intent) {
+
+ List<GroupBucket> buckets = new ArrayList<>();
+
+ TrafficTreatment.Builder tBuilderIn = DefaultTrafficTreatment.builder();
+ ConnectPoint src = links.get(0).src();
+ tBuilderIn.setOutput(src.port());
+
+ TrafficTreatment.Builder tBuilderIn2 = DefaultTrafficTreatment.builder();
+ ConnectPoint src2 = backupLinks.get(0).src();
+ tBuilderIn2.setOutput(src2.port());
+
+ buckets.add(DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn.build(), src.port(), null));
+ buckets.add(DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn2.build(), src2.port(), null));
+
+ GroupBuckets groupBuckets = new GroupBuckets(buckets);
+
+ GroupDescription groupDesc = new DefaultGroupDescription(src.deviceId(), Group.Type.FAILOVER,
+ groupBuckets, makeGroupKey(intent.id()), null, intent.appId());
+ groupService.addGroup(groupDesc);
+ }
+
+ /**
+ * Manufactures flow rule with treatment that is defined by failover
+ * group and traffic selector determined by ingress port of the intent.
+ *
+ * @param intent intent which is being compiled (for appId)
+ * @return a list of a singular flow rule with fast failover
+ * outport traffic treatment
+ */
+ private List<FlowRule> createFailoverFlowRules(PointToPointIntent intent) {
+ List<FlowRule> flowRules = new ArrayList<>();
+
+ ConnectPoint ingress = intent.ingressPoint();
+ DeviceId deviceId = ingress.deviceId();
+
+ // flow rule with failover traffic treatment
+ TrafficSelector trafficSelector = DefaultTrafficSelector.builder(intent.selector())
+ .matchInPort(ingress.port()).build();
+
+ FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder();
+ flowRules.add(flowRuleBuilder.withSelector(trafficSelector)
+ .withTreatment(buildFailoverTreatment(deviceId, makeGroupKey(intent.id())))
+ .fromApp(intent.appId())
+ .makePermanent()
+ .forDevice(deviceId)
+ .withPriority(PRIORITY)
+ .build());
+
+ return flowRules;
+ }
+
+ private TrafficTreatment buildFailoverTreatment(DeviceId srcDevice,
+ GroupKey groupKey) {
+ Group group = groupService.getGroup(srcDevice, groupKey);
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ TrafficTreatment trafficTreatment = tBuilder.group(group.id()).build();
+ return trafficTreatment;
+ }
+
+ /**
+ * Deletes intents from the given list if the ports or links the intent
+ * relies on are no longer viable. The failover flow rule intent is never
+ * deleted -- only its contents are updated.
+ *
+ * @param oldInstallables list of intents to examine
+ * @return list of reusable installable intents
+ */
+ private List<Intent> filterInvalidSubIntents(List<Intent> oldInstallables,
+ PointToPointIntent pointIntent) {
+ List<Intent> intentList = new ArrayList<>();
+ intentList.addAll(oldInstallables);
+ erasePrimary = false;
+ eraseBackup = false;
+ if (intentList != null) {
+ Iterator<Intent> iterator = intentList.iterator();
+ while (iterator.hasNext() && !(erasePrimary && eraseBackup)) {
+ Intent intent = iterator.next();
+ intent.resources().forEach(resource -> {
+ if (resource instanceof Link) {
+ Link link = (Link) resource;
+ if (link.state() == Link.State.INACTIVE) {
+ setPathsToRemove(intent);
+ } else if (link instanceof EdgeLink) {
+ ConnectPoint connectPoint = (link.src().elementId() instanceof DeviceId)
+ ? link.src() : link.dst();
+ Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+ if (port == null || !port.isEnabled()) {
+ setPathsToRemove(intent);
+ }
+ } else {
+ Port port1 = deviceService.getPort(link.src().deviceId(), link.src().port());
+ Port port2 = deviceService.getPort(link.dst().deviceId(), link.dst().port());
+ if (port1 == null || !port1.isEnabled() || port2 == null || !port2.isEnabled()) {
+ setPathsToRemove(intent);
+ }
+ }
+ }
+ });
+ }
+ removeAndUpdateIntents(intentList, pointIntent);
+ }
+ return intentList;
+ }
+
+ /**
+ * Sets instance variables erasePrimary and eraseBackup. If erasePrimary,
+ * the primary path is no longer viable and related intents will be deleted.
+ * If eraseBackup, the backup path is no longer viable and related intents
+ * will be deleted.
+ *
+ * @param intent intent whose resources are found to be disabled/inactive:
+ * if intent is part of primary path, primary path set for removal;
+ * if intent is part of backup path, backup path set for removal;
+ * if bad intent is of type failover, the ingress point is down,
+ * and both paths are rendered inactive.
+ * @return true if both primary and backup paths are to be removed
+ */
+ private boolean setPathsToRemove(Intent intent) {
+ if (intent instanceof FlowRuleIntent) {
+ FlowRuleIntent frIntent = (FlowRuleIntent) intent;
+ PathIntent.ProtectionType type = frIntent.type();
+ if (type == PathIntent.ProtectionType.PRIMARY || type == PathIntent.ProtectionType.FAILOVER) {
+ erasePrimary = true;
+ }
+ if (type == PathIntent.ProtectionType.BACKUP || type == PathIntent.ProtectionType.FAILOVER) {
+ eraseBackup = true;
+ }
+ }
+ return erasePrimary && eraseBackup;
+ }
+
+ /**
+ * Removes intents from installables list, depending on the values
+ * of instance variables erasePrimary and eraseBackup. Flow rule intents
+ * that contain the manufactured fast failover flow rules are never deleted.
+ * The contents are simply modified as necessary. If cleanUpIntents size
+ * is greater than 1 (failover intent), then one whole path from previous
+ * installables must be still viable.
+ *
+ * @param cleanUpIntents list of installable intents
+ */
+ private void removeAndUpdateIntents(List<Intent> cleanUpIntents,
+ PointToPointIntent pointIntent) {
+ ListIterator<Intent> iterator = cleanUpIntents.listIterator();
+ while (iterator.hasNext()) {
+ Intent cIntent = iterator.next();
+ if (cIntent instanceof FlowRuleIntent) {
+ FlowRuleIntent fIntent = (FlowRuleIntent) cIntent;
+ if (fIntent.type() == PathIntent.ProtectionType.PRIMARY && erasePrimary) {
+ // remove primary path's flow rule intents
+ iterator.remove();
+ } else if (fIntent.type() == PathIntent.ProtectionType.BACKUP && eraseBackup) {
+ //remove backup path's flow rule intents
+ iterator.remove();
+ } else if (fIntent.type() == PathIntent.ProtectionType.BACKUP && erasePrimary) {
+ // promote backup path's flow rule intents to primary
+ iterator.set(new FlowRuleIntent(fIntent, PathIntent.ProtectionType.PRIMARY));
+ }
+ }
+ }
+ // remove buckets whose watchports are disabled if the failover group exists
+ Group group = groupService.getGroup(pointIntent.ingressPoint().deviceId(),
+ makeGroupKey(pointIntent.id()));
+ if (group != null) {
+ updateFailoverGroup(pointIntent);
+ }
+ }
+
+ // Removes buckets whose treatments rely on disabled ports from the
+ // failover group.
+ private void updateFailoverGroup(PointToPointIntent pointIntent) {
+ DeviceId deviceId = pointIntent.ingressPoint().deviceId();
+ GroupKey groupKey = makeGroupKey(pointIntent.id());
+ Group group = groupService.getGroup(deviceId, groupKey);
+ Iterator<GroupBucket> groupIterator = group.buckets().buckets().iterator();
+ while (groupIterator.hasNext()) {
+ GroupBucket bucket = groupIterator.next();
+ Instruction individualInstruction = bucket.treatment().allInstructions().get(0);
+ if (individualInstruction instanceof Instructions.OutputInstruction) {
+ Instructions.OutputInstruction outInstruction =
+ (Instructions.OutputInstruction) individualInstruction;
+ Port port = deviceService.getPort(deviceId, outInstruction.port());
+ if (port == null || !port.isEnabled()) {
+ GroupBuckets removeBuckets = new GroupBuckets(Collections.singletonList(bucket));
+ groupService.removeBucketsFromGroup(deviceId, groupKey,
+ removeBuckets, groupKey,
+ pointIntent.appId());
+ }
+ }
+ }
+ }
+
+ // Adds failover group bucket with treatment outport determined by the
+ // ingress point of the links.
+ private void updateFailoverGroup(PointToPointIntent intent, List<Link> links) {
+ GroupKey groupKey = makeGroupKey(intent.id());
+
+ TrafficTreatment.Builder tBuilderIn = DefaultTrafficTreatment.builder();
+ ConnectPoint src = links.get(0).src();
+ tBuilderIn.setOutput(src.port());
+ GroupBucket bucket = DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn.build(), src.port(), null);
+ GroupBuckets addBuckets = new GroupBuckets(Collections.singletonList(bucket));
+
+ groupService.addBucketsToGroup(src.deviceId(), groupKey, addBuckets, groupKey, intent.appId());
+ }
}