[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;