[AETHER-1299] Implement SPINE pinning policy in SR.
Additionally introduces a new CLI command. Also this review addresses
comments coming from the previous patch [24393] and fixes some issue
seen in the previous patch.
Change-Id: I5362d95ebe1c237eb5bdb13ec34ab109d25f9f7a
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index f65d4ea..4b7a96f 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -780,11 +780,7 @@
return true;
}
- /**
- * Returns the VlanId assigned internally by default to unconfigured ports.
- *
- * @return the default internal vlan id
- */
+ @Override
public VlanId getDefaultInternalVlan() {
return VlanId.vlanId((short) defaultInternalVlan);
}
@@ -1204,6 +1200,11 @@
return deviceConfiguration.getEdgeDeviceIds();
}
+ @Override
+ public MacAddress getDeviceMacAddress(DeviceId deviceId) throws DeviceConfigNotFoundException {
+ return deviceConfiguration.getDeviceMac(deviceId);
+ }
+
/**
* Returns locations of given resolved route.
*
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index 95b5be5..4d89588 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
@@ -19,6 +19,7 @@
import org.apache.commons.lang3.NotImplementedException;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.ApplicationId;
@@ -27,6 +28,7 @@
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.grouphandler.NextNeighbors;
import org.onosproject.segmentrouting.mcast.McastFilteringObjStoreKey;
import org.onosproject.segmentrouting.mcast.McastRole;
@@ -443,4 +445,20 @@
* @return list of the edge device ids
*/
List<DeviceId> getEdgeDeviceIds();
+
+ /**
+ * Returns the configured mac address of the device.
+ *
+ * @param deviceId the device id
+ * @return the configured mac address
+ * @throws DeviceConfigNotFoundException if config is not present
+ */
+ MacAddress getDeviceMacAddress(DeviceId deviceId) throws DeviceConfigNotFoundException;
+
+ /**
+ * Returns the VlanId assigned internally by default to unconfigured ports.
+ *
+ * @return the default internal vlan id
+ */
+ VlanId getDefaultInternalVlan();
}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/cli/DropPolicyAddCommand.java b/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyDropAddCommand.java
similarity index 92%
rename from impl/src/main/java/org/onosproject/segmentrouting/cli/DropPolicyAddCommand.java
rename to impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyDropAddCommand.java
index 9c3cb51..778e4c4 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/cli/DropPolicyAddCommand.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyDropAddCommand.java
@@ -27,9 +27,9 @@
* Command to add a new drop policy.
*/
@Service
-@Command(scope = "onos", name = "sr-drop-policy-add",
+@Command(scope = "onos", name = "sr-policy-drop-add",
description = "Create a new drop policy")
-public class DropPolicyAddCommand extends AbstractShellCommand {
+public class PolicyDropAddCommand extends AbstractShellCommand {
@Override
protected void doExecute() {
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java b/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java
index 1c61912..c110358 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java
@@ -31,7 +31,7 @@
* Command to show the list of policies.
*/
@Service
-@Command(scope = "onos", name = "sr-policy-list",
+@Command(scope = "onos", name = "sr-policy",
description = "Lists all policies")
public class PolicyListCommand extends AbstractShellCommand {
@@ -40,11 +40,11 @@
private static final String FORMAT_MAPPING_OPERATION =
" op=%s";
- @Option(name = "-filt", aliases = "--filter",
+ @Option(name = "-t", aliases = "--type",
description = "Filter based on policy type",
valueToShowInHelp = "DROP",
multiValued = true)
- String[] filters = null;
+ String[] types = null;
@Override
protected void doExecute() {
@@ -55,8 +55,8 @@
private Set<Policy.PolicyType> policyTypes() {
Set<Policy.PolicyType> policyTypes = Sets.newHashSet();
- if (filters != null) {
- for (String filter : filters) {
+ if (types != null) {
+ for (String filter : types) {
policyTypes.add(Policy.PolicyType.valueOf(filter));
}
}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyRedirectAddCommand.java b/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyRedirectAddCommand.java
new file mode 100644
index 0000000..e9f0279
--- /dev/null
+++ b/impl/src/main/java/org/onosproject/segmentrouting/cli/PolicyRedirectAddCommand.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.segmentrouting.cli;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.glassfish.jersey.internal.guava.Sets;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.DeviceIdCompleter;
+import org.onosproject.net.DeviceId;
+import org.onosproject.segmentrouting.policy.api.PolicyId;
+import org.onosproject.segmentrouting.policy.api.PolicyService;
+import org.onosproject.segmentrouting.policy.api.RedirectPolicy;
+
+import java.util.Set;
+
+/**
+ * Command to add a new redirect policy.
+ */
+@Service
+@Command(scope = "onos", name = "sr-policy-redirect-add",
+ description = "Create a new redirect policy")
+public class PolicyRedirectAddCommand extends AbstractShellCommand {
+
+ @Option(name = "-s", aliases = "--spine",
+ description = "Pin to spine",
+ valueToShowInHelp = "device:spine1",
+ multiValued = true)
+ @Completion(DeviceIdCompleter.class)
+ String[] spines = null;
+
+ @Override
+ protected void doExecute() {
+ Set<DeviceId> spinesToEnforce = spinesToEnforce();
+ if (spinesToEnforce.isEmpty()) {
+ print("Unable to submit redirect policy");
+ return;
+ }
+ PolicyService policyService = AbstractShellCommand.get(PolicyService.class);
+ PolicyId policyId = policyService.addOrUpdatePolicy(new RedirectPolicy(spinesToEnforce));
+ print("Policy %s has been submitted", policyId);
+ }
+
+ private Set<DeviceId> spinesToEnforce() {
+ Set<DeviceId> spinesToEnforce = Sets.newHashSet();
+ if (spines != null) {
+ for (String spine : spines) {
+ spinesToEnforce.add(DeviceId.deviceId(spine));
+ }
+ }
+ return spinesToEnforce;
+ }
+}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchAddCommand.java b/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchAddCommand.java
index d8b7a0a..ad9a432 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchAddCommand.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchAddCommand.java
@@ -17,6 +17,7 @@
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.onlab.packet.IPv4;
@@ -37,7 +38,7 @@
* Command to add a traffic match.
*/
@Service
-@Command(scope = "onos", name = "sr-tmatch-add",
+@Command(scope = "onos", name = "sr-tm-add",
description = "Create a new traffic match")
public class TrafficMatchAddCommand extends AbstractShellCommand {
@@ -46,44 +47,53 @@
required = true, multiValued = false)
String policyId;
- @Argument(index = 1, name = "srcIp",
+ @Option(name = "-sip", aliases = "--srcIp",
description = "src IP",
- required = false, multiValued = false)
+ valueToShowInHelp = "10.0.0.1",
+ multiValued = false)
String srcIp;
- @Argument(index = 2, name = "srcPort",
+ @Option(name = "-sp", aliases = "--srcPort",
description = "src port",
- required = false, multiValued = false)
+ valueToShowInHelp = "1001",
+ multiValued = false)
short srcPort;
- @Argument(index = 3, name = "dstIp",
+ @Option(name = "-dip", aliases = "--dstIp",
description = "dst IP",
- required = false, multiValued = false)
+ valueToShowInHelp = "10.0.0.2",
+ multiValued = false)
String dstIp;
- @Argument(index = 4, name = "dstPort",
+ @Option(name = "-dp", aliases = "--dstPort",
description = "dst port",
- required = false, multiValued = false)
+ valueToShowInHelp = "1002",
+ multiValued = false)
short dstPort;
- @Argument(index = 5, name = "proto",
+ @Option(name = "-p", aliases = "--proto",
description = "IP protocol",
- required = false, multiValued = false)
+ valueToShowInHelp = "0x11",
+ multiValued = false)
String proto;
- @Argument(index = 6, name = "srcMac",
+ // TODO Consider to filter out the following fields for red policies
+ @Option(name = "-smac", aliases = "--srcMac",
description = "src MAC",
- required = false, multiValued = false)
+ valueToShowInHelp = "00:00:00:00:00:01",
+ multiValued = false)
String srcMac;
- @Argument(index = 7, name = "dstMac",
+ @Option(name = "-dmac", aliases = "--dstMac",
description = "dst MAC",
- required = false, multiValued = false)
+ valueToShowInHelp = "00:00:00:00:00:02",
+ multiValued = false)
String dstMac;
- @Argument(index = 8, name = "vlanId",
- description = "VLAN id",
- required = false, multiValued = false)
+ @Option(name = "-vid", aliases = "--VlanId",
+ description = "vlan ID",
+ valueToShowInHelp = "10",
+ multiValued = false)
short vlanId = -1;
@Override
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchListCommand.java b/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchListCommand.java
index c00d1fa..cb5008a 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchListCommand.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchListCommand.java
@@ -26,12 +26,12 @@
* Command to show the list of traffic matches.
*/
@Service
-@Command(scope = "onos", name = "sr-tmatch-list",
+@Command(scope = "onos", name = "sr-tm",
description = "Lists all traffic matches")
public class TrafficMatchListCommand extends AbstractShellCommand {
private static final String FORMAT_MAPPING_TRAFFIC_MATCH =
- " id=%s, state=%s";
+ " id=%s, state=%s, policyId=%s";
private static final String FORMAT_MAPPING_OPERATION =
" op=%s";
@@ -43,7 +43,7 @@
private void printTrafficMatch(TrafficMatchData trafficMatchData) {
print(FORMAT_MAPPING_TRAFFIC_MATCH, trafficMatchData.trafficMatch().trafficMatchId(),
- trafficMatchData.trafficMatchState());
+ trafficMatchData.trafficMatchState(), trafficMatchData.trafficMatch().policyId());
trafficMatchData.operations().forEach(operation -> print(FORMAT_MAPPING_OPERATION, operation));
}
}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchRemoveCommand.java b/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchRemoveCommand.java
index 763e9c8..f6f0212 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchRemoveCommand.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/cli/TrafficMatchRemoveCommand.java
@@ -28,7 +28,7 @@
* Command to remove a traffic match.
*/
@Service
-@Command(scope = "onos", name = "sr-tmatch-remove",
+@Command(scope = "onos", name = "sr-tm-remove",
description = "Remove a traffic match")
public class TrafficMatchRemoveCommand extends AbstractShellCommand {
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/AbstractPolicy.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/AbstractPolicy.java
index 4be54a7..0ba6d05 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/AbstractPolicy.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/AbstractPolicy.java
@@ -42,6 +42,13 @@
return policyType;
}
+ /**
+ * Computes the policy id. The actual computation is left to
+ * the implementation class that can decide how to generate the
+ * policy id.
+ *
+ * @return the computed policy id
+ */
protected abstract PolicyId computePolicyId();
}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/PolicyState.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/PolicyState.java
index ad767ec..290852d 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/PolicyState.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/PolicyState.java
@@ -18,6 +18,7 @@
/**
* Represents the state of a policy as seen by the system.
*/
+// TODO consider to add a FAILED state for an invalid policy that cannot be fulfilled even after a retry
public enum PolicyState {
/**
* The policy is in the process of being added.
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/RedirectPolicy.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/RedirectPolicy.java
new file mode 100644
index 0000000..d64d487
--- /dev/null
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/RedirectPolicy.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.segmentrouting.policy.api;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Implementation of the redirect policy.
+ */
+public final class RedirectPolicy extends AbstractPolicy {
+ private List<DeviceId> spinesToEnforce = Lists.newArrayList();
+
+ /**
+ * Builds up a REDIRECT policy.
+ *
+ * @param spines the spines to enforce
+ */
+ public RedirectPolicy(Set<DeviceId> spines) {
+ super(PolicyType.REDIRECT);
+ checkArgument(!spines.isEmpty(), "Must have at least one spine");
+ // Creates an ordered set
+ TreeSet<DeviceId> sortedSpines = Sets.newTreeSet(Comparator.comparing(DeviceId::toString));
+ sortedSpines.addAll(spines);
+ spinesToEnforce.addAll(sortedSpines);
+ policyId = computePolicyId();
+ }
+
+ /**
+ * Returns the spines to be enforced during the path computation.
+ *
+ * @return the spines to be enforced
+ */
+ public List<DeviceId> spinesToEnforce() {
+ return spinesToEnforce;
+ }
+
+ @Override
+ protected PolicyId computePolicyId() {
+ return PolicyId.of(policyType().name() + spinesToEnforce);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RedirectPolicy)) {
+ return false;
+ }
+ final RedirectPolicy other = (RedirectPolicy) obj;
+ return Objects.equals(policyType(), other.policyType()) &&
+ Objects.equals(policyId(), other.policyId()) &&
+ Objects.equals(spinesToEnforce, other.spinesToEnforce);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(policyId(), policyType(), spinesToEnforce);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("policyId", policyId())
+ .add("policyType", policyType())
+ .add("spinesToEnforce", spinesToEnforce)
+ .toString();
+ }
+}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/TrafficMatchState.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/TrafficMatchState.java
index f775c50..ab79e2f 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/api/TrafficMatchState.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/api/TrafficMatchState.java
@@ -18,6 +18,7 @@
/**
* Represents the state of a traffic match as seen by the system.
*/
+// TODO consider to add a FAILED state for an invalid traffic match that cannot be fulfilled even after a retry
public enum TrafficMatchState {
/**
* The traffic match is in the process of being added.
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/Operation.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/Operation.java
index 99c9fd4..4f07972 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/Operation.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/Operation.java
@@ -167,6 +167,8 @@
/**
* Creates a new operation builder using the supplied operation.
+ * The boolean isDone and the objective operation won't be copied
+ * by the supplied operation.
*
* @param operation the operation
* @return an operation builder
@@ -191,8 +193,6 @@
private Builder(Operation operation) {
isInstall = operation.isInstall();
- isDone = operation.isDone();
- objectiveOperation = operation.objectiveOperation();
policy = operation.policy().orElse(null);
trafficMatch = operation.trafficMatch().orElse(null);
}
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyKey.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyKey.java
index e0eac06..64b2bbb 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyKey.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyKey.java
@@ -22,7 +22,12 @@
import java.util.StringTokenizer;
/**
- * Policy key used by the store.
+ * Policy key used by the store to track the operations ongoing on the devices.
+ *
+ * Policy is the high level intent expressed by the user that gets translated by
+ * the PolicyManager in modifications operated on the devices. The policy operations
+ * are more important than the policy itself without them the policy cannot be
+ * considered fulfilled.
*/
public class PolicyKey {
private DeviceId deviceId;
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyManager.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyManager.java
index dbd5667..8f7c69c 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyManager.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/PolicyManager.java
@@ -21,23 +21,32 @@
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import org.glassfish.jersey.internal.guava.Sets;
+import org.onlab.packet.MacAddress;
import org.onlab.util.KryoNamespace;
import org.onlab.util.PredictableExecutor;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
import org.onosproject.net.flowobjective.DefaultObjectiveContext;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.intent.WorkPartitionService;
+import org.onosproject.net.link.LinkService;
import org.onosproject.segmentrouting.SegmentRoutingService;
+import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.policy.api.DropPolicy;
import org.onosproject.segmentrouting.policy.api.Policy;
import org.onosproject.segmentrouting.policy.api.Policy.PolicyType;
@@ -45,6 +54,7 @@
import org.onosproject.segmentrouting.policy.api.PolicyId;
import org.onosproject.segmentrouting.policy.api.PolicyService;
import org.onosproject.segmentrouting.policy.api.PolicyState;
+import org.onosproject.segmentrouting.policy.api.RedirectPolicy;
import org.onosproject.segmentrouting.policy.api.TrafficMatch;
import org.onosproject.segmentrouting.policy.api.TrafficMatchData;
import org.onosproject.segmentrouting.policy.api.TrafficMatchId;
@@ -86,9 +96,18 @@
private Logger log = getLogger(getClass());
static final String KEY_SEPARATOR = "|";
+ // Supported policies
+ private static final Set<Policy.PolicyType> SUPPORTED_POLICIES = ImmutableSet.of(
+ PolicyType.DROP, PolicyType.REDIRECT);
+
+ // Driver should use this meta to match port_is_edge field in the ACL table
+ private static final long EDGE_PORT = 1;
+ private static final long INFRA_PORT = 0;
+
// Policy/TrafficMatch store related objects. We use these consistent maps to keep track of the
// lifecycle of a policy/traffic match. These are decomposed in multiple operations which have
// to be performed on multiple devices in order to have a policy/traffic match in ADDED state.
+ // TODO Consider to add store and delegate
private static final String POLICY_STORE = "sr-policy-store";
private ConsistentMap<PolicyId, PolicyRequest> policies;
private MapEventListener<PolicyId, PolicyRequest> mapPolListener = new InternalPolMapEventListener();
@@ -120,6 +139,7 @@
.register(PolicyId.class)
.register(PolicyType.class)
.register(DropPolicy.class)
+ .register(RedirectPolicy.class)
.register(PolicyState.class)
.register(PolicyRequest.class)
.register(TrafficMatchId.class)
@@ -139,13 +159,16 @@
private ClusterService clusterService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
- public WorkPartitionService workPartitionService;
+ private WorkPartitionService workPartitionService;
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
- public SegmentRoutingService srService;
+ private SegmentRoutingService srService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
- public FlowObjectiveService flowObjectiveService;
+ private FlowObjectiveService flowObjectiveService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ private LinkService linkService;
@Activate
public void activate() {
@@ -211,6 +234,12 @@
@Override
public boolean removePolicy(PolicyId policyId) {
boolean result;
+ if (dependingTrafficMatches(policyId).isPresent()) {
+ if (log.isDebugEnabled()) {
+ log.debug("Found depending traffic matches");
+ }
+ return false;
+ }
try {
result = Versioned.valueOrNull(policies.computeIfPresent(policyId, (k, v) -> {
if (v.policyState() != PolicyState.PENDING_REMOVE) {
@@ -332,39 +361,74 @@
// Orchestrate policy installation according to the type
private void installPolicyInDevice(DeviceId deviceId, Policy policy) {
- PolicyKey policyKey;
- Operation operation;
- if (policy.policyType() == PolicyType.DROP) {
- if (log.isDebugEnabled()) {
- log.debug("Installing DROP policy {}", policy.policyId());
- }
- // DROP policies do not need the next objective installation phase
- // we can update directly the map and signal the ops as done
- policyKey = new PolicyKey(deviceId, policy.policyId());
- operation = Operation.builder()
- .isDone(true)
- .isInstall(true)
- .policy(policy)
- .build();
- operations.put(policyKey.toString(), operation);
- } else if (policy.policyType() == PolicyType.REDIRECT) {
- if (log.isDebugEnabled()) {
- log.debug("Installing REDIRECT policy {}", policy.policyId());
- }
- // REDIRECT Uses objective context to update the ops as done when it returns
- // successfully. In the other cases leaves the ops as undone and the
- // relative policy will remain in pending.
- } else {
+ if (!SUPPORTED_POLICIES.contains(policy.policyType())) {
log.warn("Policy {} type {} not yet supported",
policy.policyId(), policy.policyType());
+ return;
+ }
+ PolicyKey policyKey;
+ Operation.Builder operation;
+ if (log.isDebugEnabled()) {
+ log.debug("Installing {} policy {} for dev: {}",
+ policy.policyType(), policy.policyId(), deviceId);
+ }
+ policyKey = new PolicyKey(deviceId, policy.policyId());
+ operation = Operation.builder()
+ .isInstall(true)
+ .policy(policy);
+ // TODO To better handle different policy types consider the abstraction of a compiler (subtypes ?)
+ if (policy.policyType() == PolicyType.DROP) {
+ // DROP policies do not need the next objective installation phase
+ // we can update directly the map and signal the ops as done
+ operation.isDone(true);
+ operations.put(policyKey.toString(), operation.build());
+ } else if (policy.policyType() == PolicyType.REDIRECT) {
+ // REDIRECT Uses next objective context to update the ops as done when
+ // it returns successfully. In the other cases leaves the ops as undone
+ // and the relative policy will remain in pending.
+ operations.put(policyKey.toString(), operation.build());
+ NextObjective.Builder builder = redirectPolicyNextObjective(deviceId, (RedirectPolicy) policy);
+ // Handle error here - leave the operation as undone and pending
+ if (builder != null) {
+ CompletableFuture<Objective> future = new CompletableFuture<>();
+ if (log.isDebugEnabled()) {
+ log.debug("Installing REDIRECT next objective for dev: {}", deviceId);
+ }
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) -> {
+ if (log.isDebugEnabled()) {
+ log.debug("REDIRECT next objective for policy {} installed in dev: {}",
+ policy.policyId(), deviceId);
+ }
+ future.complete(objective);
+ },
+ (objective, error) -> {
+ log.warn("Failed to install REDIRECT next objective for policy {}: {} in dev: {}",
+ policy.policyId(), error, deviceId);
+ future.complete(null);
+ });
+ // Context is not serializable
+ NextObjective serializableObjective = builder.add();
+ flowObjectiveService.next(deviceId, builder.add(context));
+ future.whenComplete((objective, ex) -> {
+ if (ex != null) {
+ log.error("Exception installing REDIRECT next objective", ex);
+ } else if (objective != null) {
+ operations.computeIfPresent(policyKey.toString(), (k, v) -> {
+ if (!v.isDone() && v.isInstall()) {
+ v.isDone(true);
+ v.objectiveOperation(serializableObjective);
+ }
+ return v;
+ });
+ }
+ });
+ }
}
}
// Remove policy in a device according to the type
private void removePolicyInDevice(DeviceId deviceId, Policy policy) {
- if (log.isDebugEnabled()) {
- log.debug("Removing policy {}", policy.policyId());
- }
PolicyKey policyKey = new PolicyKey(deviceId, policy.policyId());
Operation operation = Versioned.valueOrNull(operations.get(policyKey.toString()));
// Policy might be still in pending or not present anymore
@@ -377,24 +441,52 @@
.build();
operations.put(policyKey.toString(), operation);
} else {
+ if (log.isDebugEnabled()) {
+ log.debug("Removing {} policy {} in device {}", policy.policyType(), policy.policyId(), deviceId);
+ }
+ Operation.Builder operationBuilder = Operation.builder()
+ .isInstall(false)
+ .policy(policy);
if (policy.policyType() == PolicyType.DROP) {
- if (log.isDebugEnabled()) {
- log.debug("Removing DROP policy {}", policy.policyId());
- }
- operation = Operation.builder()
- .isDone(true)
- .isInstall(false)
- .policy(policy)
- .build();
- operations.put(policyKey.toString(), operation);
+ operationBuilder.isDone(true);
+ operations.put(policyKey.toString(), operationBuilder.build());
} else if (policy.policyType() == PolicyType.REDIRECT) {
+ // REDIRECT has to remove the next objective first
+ NextObjective oldObj = (NextObjective) operation.objectiveOperation();
+ operations.put(policyKey.toString(), operationBuilder.build());
+ NextObjective.Builder builder = oldObj.copy();
+ CompletableFuture<Objective> future = new CompletableFuture<>();
if (log.isDebugEnabled()) {
- log.debug("Removing REDIRECT policy {}", policy.policyId());
+ log.debug("Removing REDIRECT next objective for dev: {}", deviceId);
}
- // REDIRECT has to remove first a next objective
- } else {
- log.warn("Policy {} type {} not yet supported",
- policy.policyId(), policy.policyType());
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) -> {
+ if (log.isDebugEnabled()) {
+ log.debug("REDIRECT next objective for policy {} removed in dev: {}",
+ policy.policyId(), deviceId);
+ }
+ future.complete(objective);
+ },
+ (objective, error) -> {
+ log.warn("Failed to remove REDIRECT next objective for policy {}: {} in dev: {}",
+ policy.policyId(), error, deviceId);
+ future.complete(null);
+ });
+ NextObjective serializableObjective = builder.remove();
+ flowObjectiveService.next(deviceId, builder.remove(context));
+ future.whenComplete((objective, ex) -> {
+ if (ex != null) {
+ log.error("Exception Removing REDIRECT next objective", ex);
+ } else if (objective != null) {
+ operations.computeIfPresent(policyKey.toString(), (k, v) -> {
+ if (!v.isDone() && !v.isInstall()) {
+ v.isDone(true);
+ v.objectiveOperation(serializableObjective);
+ }
+ return v;
+ });
+ }
+ });
}
}
}
@@ -468,67 +560,79 @@
log.debug("Installing traffic match {} associated to policy {}",
trafficMatch.trafficMatchId(), trafficMatch.policyId());
}
- // Updates the store and then send the versatile fwd objective to the pipeliner
TrafficMatchKey trafficMatchKey = new TrafficMatchKey(deviceId, trafficMatch.trafficMatchId());
- Operation trafficOperation = Operation.builder()
- .isInstall(true)
- .trafficMatch(trafficMatch)
- .build();
- operations.put(trafficMatchKey.toString(), trafficOperation);
+ Operation trafficOperation = Versioned.valueOrNull(operations.get(trafficMatchKey.toString()));
+ if (trafficOperation != null && trafficOperation.isInstall()) {
+ if (log.isDebugEnabled()) {
+ log.debug("There is already an install operation for traffic match {} associated to policy {} " +
+ "for device {}", trafficMatch.trafficMatchId(), trafficMatch.policyId(), deviceId);
+ }
+ return;
+ }
// For the DROP policy we need to set an ACL drop in the fwd objective. The other
// policies require to retrieve the next Id and sets the next step.
PolicyKey policyKey = new PolicyKey(deviceId, trafficMatch.policyId());
Operation policyOperation = Versioned.valueOrNull(operations.get(policyKey.toString()));
if (policyOperation == null || !policyOperation.isDone() ||
- !policyOperation.isInstall() || policyOperation.policy().isEmpty()) {
+ !policyOperation.isInstall() || policyOperation.policy().isEmpty() ||
+ (policyOperation.policy().get().policyType() == PolicyType.REDIRECT &&
+ policyOperation.objectiveOperation() == null)) {
log.info("Deferring traffic match {} installation on device {}. Policy {} not yet installed",
trafficMatch.trafficMatchId(), deviceId, trafficMatch.policyId());
return;
}
+ // Updates the store and then send the versatile fwd objective to the pipeliner
+ trafficOperation = Operation.builder()
+ .isInstall(true)
+ .trafficMatch(trafficMatch)
+ .build();
+ operations.put(trafficMatchKey.toString(), trafficOperation);
Policy policy = policyOperation.policy().get();
- ForwardingObjective.Builder builder = trafficMatchFwdObjective(trafficMatch);
- // TODO we can try to reuse some code: context and completable future logic
+ ForwardingObjective.Builder builder = trafficMatchFwdObjective(trafficMatch, policy.policyType());
if (policy.policyType() == PolicyType.DROP) {
- // Firstly builds the fwd objective with the wipeDeferred action. Once, the fwd
- // objective has completed its execution, we update the policiesOps map
+ // Firstly builds the fwd objective with the wipeDeferred action.
TrafficTreatment dropTreatment = DefaultTrafficTreatment.builder()
.wipeDeferred()
.build();
builder.withTreatment(dropTreatment);
- CompletableFuture<Objective> future = new CompletableFuture<>();
- if (log.isDebugEnabled()) {
- log.debug("Installing ACL drop forwarding objectives for dev: {}", deviceId);
- }
- ObjectiveContext context = new DefaultObjectiveContext(
- (objective) -> {
- if (log.isDebugEnabled()) {
- log.debug("ACL drop rule for policy {} installed", trafficMatch.policyId());
- }
- future.complete(objective);
- },
- (objective, error) -> {
- log.warn("Failed to install ACL drop rule for policy {}: {}", trafficMatch.policyId(), error);
- future.complete(null);
- });
- // Context is not serializable
- ForwardingObjective serializableObjective = builder.add();
- flowObjectiveService.forward(deviceId, builder.add(context));
- future.whenComplete((objective, ex) -> {
- if (ex != null) {
- log.error("Exception installing ACL drop rule", ex);
- } else if (objective != null) {
- operations.computeIfPresent(trafficMatchKey.toString(), (k, v) -> {
- if (!v.isDone() && v.isInstall()) {
- v.isDone(true);
- v.objectiveOperation(serializableObjective);
- }
- return v;
- });
- }
- });
- } else {
- log.warn("Policy {} type {} not yet supported", policy.policyId(), policy.policyType());
+ } else if (policy.policyType() == PolicyType.REDIRECT) {
+
+ // Here we need to set only the next step
+ builder.nextStep(policyOperation.objectiveOperation().id());
}
+ // Once, the fwd objective has completed its execution, we update the policiesOps map
+ CompletableFuture<Objective> future = new CompletableFuture<>();
+ if (log.isDebugEnabled()) {
+ log.debug("Installing forwarding objective for dev: {}", deviceId);
+ }
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) -> {
+ if (log.isDebugEnabled()) {
+ log.debug("Forwarding objective for policy {} installed", trafficMatch.policyId());
+ }
+ future.complete(objective);
+ },
+ (objective, error) -> {
+ log.warn("Failed to install forwarding objective for policy {}: {}",
+ trafficMatch.policyId(), error);
+ future.complete(null);
+ });
+ // Context is not serializable
+ ForwardingObjective serializableObjective = builder.add();
+ flowObjectiveService.forward(deviceId, builder.add(context));
+ future.whenComplete((objective, ex) -> {
+ if (ex != null) {
+ log.error("Exception installing forwarding objective", ex);
+ } else if (objective != null) {
+ operations.computeIfPresent(trafficMatchKey.toString(), (k, v) -> {
+ if (!v.isDone() && v.isInstall()) {
+ v.isDone(true);
+ v.objectiveOperation(serializableObjective);
+ }
+ return v;
+ });
+ }
+ });
}
// Updates traffic match status if all the pending ops are done
@@ -597,34 +701,39 @@
.trafficMatch(trafficMatch)
.build();
operations.put(trafficMatchKey.toString(), operation);
+ } else if (!operation.isInstall()) {
+ if (log.isDebugEnabled()) {
+ log.debug("There is already an uninstall operation for traffic match {} associated to policy {}" +
+ " for device {}", trafficMatch.trafficMatchId(), trafficMatch.policyId(), deviceId);
+ }
} else {
ForwardingObjective oldObj = (ForwardingObjective) operation.objectiveOperation();
operation = Operation.builder(operation)
- .isDone(false)
.isInstall(false)
.build();
operations.put(trafficMatchKey.toString(), operation);
ForwardingObjective.Builder builder = DefaultForwardingObjective.builder(oldObj);
CompletableFuture<Objective> future = new CompletableFuture<>();
if (log.isDebugEnabled()) {
- log.debug("Removing ACL drop forwarding objectives for dev: {}", deviceId);
+ log.debug("Removing forwarding objectives for dev: {}", deviceId);
}
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> {
if (log.isDebugEnabled()) {
- log.debug("ACL drop rule for policy {} removed", trafficMatch.policyId());
+ log.debug("Forwarding objective for policy {} removed", trafficMatch.policyId());
}
future.complete(objective);
},
(objective, error) -> {
- log.warn("Failed to remove ACL drop rule for policy {}: {}", trafficMatch.policyId(), error);
+ log.warn("Failed to remove forwarding objective for policy {}: {}",
+ trafficMatch.policyId(), error);
future.complete(null);
});
ForwardingObjective serializableObjective = builder.remove();
flowObjectiveService.forward(deviceId, builder.remove(context));
future.whenComplete((objective, ex) -> {
if (ex != null) {
- log.error("Exception removing ACL drop rule", ex);
+ log.error("Exception removing forwarding objective", ex);
} else if (objective != null) {
operations.computeIfPresent(trafficMatchKey.toString(), (k, v) -> {
if (!v.isDone() && !v.isInstall()) {
@@ -638,32 +747,13 @@
}
}
- // Update any depending traffic match on the policy. It is used when a policy
- // has been removed but there are still traffic matches depending on it
- private void updateDependingTrafficMatches(PolicyId policyId) {
- if (!isLeader(policyId)) {
- if (log.isDebugEnabled()) {
- log.debug("Instance is not leader for policy {}", policyId);
- }
- return;
- }
- workers.execute(() -> updateDependingTrafficMatchesInternal(policyId), policyId.hashCode());
- }
-
- private void updateDependingTrafficMatchesInternal(PolicyId policyId) {
- Set<TrafficMatchRequest> pendingTrafficMatches = trafficMatches.stream()
+ // It is used when a policy has been removed but there are still traffic matches depending on it
+ private Optional<TrafficMatchRequest> dependingTrafficMatches(PolicyId policyId) {
+ return trafficMatches.stream()
.filter(trafficMatchEntry -> trafficMatchEntry.getValue().value().policyId().equals(policyId) &&
trafficMatchEntry.getValue().value().trafficMatchState() == TrafficMatchState.ADDED)
.map(trafficMatchEntry -> trafficMatchEntry.getValue().value())
- .collect(Collectors.toSet());
- for (TrafficMatchRequest trafficMatchRequest : pendingTrafficMatches) {
- trafficMatches.computeIfPresent(trafficMatchRequest.trafficMatchId(), (k, v) -> {
- if (v.trafficMatchState() == TrafficMatchState.ADDED) {
- v.trafficMatchState(TrafficMatchState.PENDING_REMOVE);
- }
- return v;
- });
- }
+ .findFirst();
}
// Utility that removes operations related to a policy or to a traffic match.
@@ -688,15 +778,67 @@
}
}
- private ForwardingObjective.Builder trafficMatchFwdObjective(TrafficMatch trafficMatch) {
+ private ForwardingObjective.Builder trafficMatchFwdObjective(TrafficMatch trafficMatch, PolicyType policyType) {
+ TrafficSelector.Builder metaBuilder = DefaultTrafficSelector.builder(trafficMatch.trafficSelector());
+ if (policyType == PolicyType.REDIRECT) {
+ metaBuilder.matchMetadata(EDGE_PORT);
+ }
return DefaultForwardingObjective.builder()
.withPriority(PolicyService.TRAFFIC_MATCH_PRIORITY)
.withSelector(trafficMatch.trafficSelector())
+ .withMeta(metaBuilder.build())
.fromApp(appId)
.withFlag(ForwardingObjective.Flag.VERSATILE)
.makePermanent();
}
+ private NextObjective.Builder redirectPolicyNextObjective(DeviceId srcDevice, RedirectPolicy redirectPolicy) {
+ Set<Link> egressLinks = linkService.getDeviceEgressLinks(srcDevice);
+ Map<ConnectPoint, DeviceId> egressPortsToEnforce = Maps.newHashMap();
+ List<DeviceId> edgeDevices = srService.getEdgeDeviceIds();
+ egressLinks.stream()
+ .filter(link -> redirectPolicy.spinesToEnforce().contains(link.dst().deviceId()) &&
+ !edgeDevices.contains(link.dst().deviceId()))
+ .forEach(link -> egressPortsToEnforce.put(link.src(), link.dst().deviceId()));
+ // No ports no friend
+ if (egressPortsToEnforce.isEmpty()) {
+ log.warn("There are no port available for the REDIRECT policy {}", redirectPolicy.policyId());
+ return null;
+ }
+ // We need to add a treatment for each valid egress port. The treatment
+ // requires to set src and dst mac address and set the egress port. We are
+ // deliberately not providing the metadata to prevent the programming of
+ // some tables which are already controlled by SegmentRouting or are unnecessary
+ int nextId = flowObjectiveService.allocateNextId();
+ DefaultNextObjective.Builder builder = DefaultNextObjective.builder()
+ .withId(nextId)
+ .withType(NextObjective.Type.HASHED)
+ .fromApp(appId);
+ MacAddress srcDeviceMac;
+ try {
+ srcDeviceMac = srService.getDeviceMacAddress(srcDevice);
+ } catch (DeviceConfigNotFoundException e) {
+ log.warn(e.getMessage() + " Aborting installation REDIRECT policy {}", redirectPolicy.policyId());
+ return null;
+ }
+ MacAddress neigborDeviceMac;
+ TrafficTreatment.Builder tBuilder;
+ for (Map.Entry<ConnectPoint, DeviceId> entry : egressPortsToEnforce.entrySet()) {
+ try {
+ neigborDeviceMac = srService.getDeviceMacAddress(entry.getValue());
+ } catch (DeviceConfigNotFoundException e) {
+ log.warn(e.getMessage() + " Aborting installation REDIRECT policy {}", redirectPolicy.policyId());
+ return null;
+ }
+ tBuilder = DefaultTrafficTreatment.builder()
+ .setEthSrc(srcDeviceMac)
+ .setEthDst(neigborDeviceMac)
+ .setOutput(entry.getKey().port());
+ builder.addTreatment(tBuilder.build());
+ }
+ return builder;
+ }
+
// Each map has an event listener enabling the events distribution across the cluster
private class InternalPolMapEventListener implements MapEventListener<PolicyId, PolicyRequest> {
@Override
@@ -723,7 +865,6 @@
break;
case REMOVE:
removeOperations(policy.policyId(), Optional.empty());
- updateDependingTrafficMatches(policy.policyId());
break;
default:
log.warn("Unknown event type {}", event.type());
@@ -812,6 +953,8 @@
.asLong();
}
+ // TODO Periodic checker, consider to add store and delegates.
+
// Check periodically for any issue and try to resolve automatically if possible
private final class PolicyChecker implements Runnable {
@Override
diff --git a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/TrafficMatchKey.java b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/TrafficMatchKey.java
index 68181a6..44e618e 100644
--- a/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/TrafficMatchKey.java
+++ b/impl/src/main/java/org/onosproject/segmentrouting/policy/impl/TrafficMatchKey.java
@@ -22,7 +22,12 @@
import java.util.StringTokenizer;
/**
- * Traffic match key used by the store.
+ * Traffic match key used by the store to track the operations ongoing on the devices.
+ *
+ * Traffic match is the high level intent expressed by the user that gets translated by
+ * the PolicyManager in modifications operated on the devices. The policy operations
+ * are more important than the policy itself without them the policy cannot be
+ * considered fulfilled.
*/
public class TrafficMatchKey {
private DeviceId deviceId;