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