helenyrwu
Committed by Gerrit Code Review

Implement path protection for point to point intents

Change-Id: I3f3627e7c2a7e3ab017e46655692ab70fdeae413
......@@ -17,6 +17,7 @@ package org.onosproject.cli.net;
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.Constraint;
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 @@ public class AddPointToPointIntentCommand extends ConnectivityIntentCommand {
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 @@ public class AddPointToPointIntentCommand extends ConnectivityIntentCommand {
TrafficTreatment treatment = buildTrafficTreatment();
List<Constraint> constraints = buildConstraints();
if (backup) {
constraints.add(new ProtectionConstraint());
}
Intent intent = PointToPointIntent.builder()
.appId(appId())
......
......@@ -35,6 +35,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
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 @@ public class FlowRuleIntent extends Intent {
}
/**
* 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 class FlowRuleIntent extends Intent {
*/
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 @@ public class FlowRuleIntent extends Intent {
protected FlowRuleIntent() {
super();
this.flowRules = null;
this.type = PathIntent.ProtectionType.PRIMARY;
}
/**
......@@ -84,6 +122,10 @@ public class FlowRuleIntent extends Intent {
return true;
}
public PathIntent.ProtectionType type() {
return type;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
......
......@@ -31,7 +31,7 @@ public interface IntentCompiler<T extends Intent> {
* 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
*/
......
......@@ -36,10 +36,11 @@ import static com.google.common.base.Preconditions.checkArgument;
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 @@ public class PathIntent extends ConnectivityIntent {
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);
PathIntent.validate(path.links());
this.path = path;
this.type = type;
}
/**
......@@ -69,6 +98,7 @@ public class PathIntent extends ConnectivityIntent {
protected PathIntent() {
super();
this.path = null;
this.type = ProtectionType.PRIMARY;
}
/**
......@@ -85,6 +115,7 @@ public class PathIntent extends ConnectivityIntent {
*/
public static class Builder extends ConnectivityIntent.Builder {
Path path;
ProtectionType type;
protected Builder() {
// Hide default constructor
......@@ -131,6 +162,11 @@ public class PathIntent extends ConnectivityIntent {
return this;
}
public Builder setType(ProtectionType type) {
this.type = type;
return this;
}
/**
* Builds a path intent from the accumulated parameters.
*
......@@ -145,7 +181,8 @@ public class PathIntent extends ConnectivityIntent {
treatment,
path,
constraints,
priority
priority,
type == null ? ProtectionType.PRIMARY : type
);
}
}
......@@ -183,6 +220,10 @@ public class PathIntent extends ConnectivityIntent {
return path;
}
public ProtectionType type() {
return type;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
......@@ -195,7 +236,25 @@ public class PathIntent extends ConnectivityIntent {
.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
}
}
......
/*
* 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;
}
}
......@@ -26,7 +26,7 @@ import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBa
public class PointToPointIntentTest extends ConnectivityIntentTest {
/**
* Checks that the MultiPointToSinglePointIntent class is immutable.
* Checks that the PointToPointIntent class is immutable.
*/
@Test
public void checkImmutability() {
......
......@@ -28,8 +28,11 @@ import org.onosproject.cfg.ComponentConfigService;
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.IntentState;
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 @@ public class IntentManager
@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 @@ public class IntentManager
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
......
......@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList;
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 @@ public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent>
}
/**
* 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 {
......
......@@ -82,7 +82,7 @@ public class PathIntentCompiler
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
......
......@@ -18,17 +18,51 @@ package org.onosproject.net.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.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 @@ public class PointToPointIntentCompiler
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,10 +109,34 @@ public class PointToPointIntentCompiler
ConnectPoint egressPoint = intent.egressPoint();
if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
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));
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());
......@@ -76,7 +146,123 @@ public class PointToPointIntentCompiler
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 @@ public class PointToPointIntentCompiler
*
* @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 @@ public class PointToPointIntentCompiler
.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());
}
}
......