Added basic pseudowire support for Trellis.

- Configurable pseudowires supporting untagged-untagged, single-single, double-double tagged traffic.
- Commands for listing, adding and removing pseudowires.
- Initial support for link failures.
- Pseudowires also configurable by network configuration.
- Tested with ofdpa_3.0.5.

Known limitations that I plan to fix soon :
	- Adding pseudowires from configuration is inconvenient because we need to ammend new pws to the
	  existing configuration. We should create a REST API for adding/removing/listing pws and abandond the
	  network configuration.
	- Spine fabric switches have rules matching a special mpls tag used for pw traffic for a specific leaf.
	  However, this rules redirect to an "indirect" group for forwarding traffic. If the resulting port there
	  is no mechanism as of now to handle this. We should use the MPLS ECMP Groups of ofdpa, however they are
	  not functional. Thus, we need to inject logic into the application to handle this case.

Change-Id: Ia85cf4514ebab627fc6ed5a19ad9f6cdc67dc24c
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
index 4c7e3d2..8b334ea 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
@@ -25,6 +25,7 @@
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.DisjointPath;
 import org.onosproject.net.Link;
@@ -52,6 +53,7 @@
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
 import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.Versioned;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,9 +62,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
-import static com.google.common.base.Preconditions.checkState;
 import static org.onosproject.net.flowobjective.ForwardingObjective.Flag.VERSATILE;
-import static org.onosproject.segmentrouting.pwaas.L2Mode.TAGGED;
 import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.INITIATION;
 import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.TERMINATION;
 import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Result.*;
@@ -75,11 +75,6 @@
 public class L2TunnelHandler {
 
     private static final Logger log = LoggerFactory.getLogger(L2TunnelHandler.class);
-    /**
-     * Error message for invalid paths.
-     */
-    private static final String WRONG_TOPOLOGY = "Path in leaf-spine topology" +
-            " should always be two hops: ";
 
     private final SegmentRoutingManager srManager;
     /**
@@ -90,16 +85,17 @@
      * To store the next objectives related to the termination.
      */
     private final ConsistentMap<String, NextObjective> l2TerminationNextObjStore;
+
     /**
-     * TODO a proper store is necessary to handle the policies, collisions and recovery.
-     * We should have a proper store for the policies and the tunnels. For several reasons:
-     * 1) We should avoid the overlapping of different policies;
-     * 2) We should avoid the overlapping of different tunnels;
-     * 3) We should have a proper mechanism for the protection;
-     * The most important one is 3). At least for 3.0 EA0 was not possible
-     * to remove the bucket, so we need a mapping between policies and tunnel
-     * in order to proper update the fwd objective for the recovery of a fault.
+     * To store policies.
      */
+    private final ConsistentMap<String, DefaultL2TunnelPolicy> l2PolicyStore;
+
+    /**
+     * To store tunnels.
+     */
+    private final ConsistentMap<String, DefaultL2Tunnel> l2TunnelStore;
+
     private final KryoNamespace.Builder l2TunnelKryo;
 
     /**
@@ -111,19 +107,105 @@
     public L2TunnelHandler(SegmentRoutingManager segmentRoutingManager) {
         srManager = segmentRoutingManager;
         l2TunnelKryo = new KryoNamespace.Builder()
-                .register(KryoNamespaces.API);
+                .register(KryoNamespaces.API)
+                .register(DefaultL2Tunnel.class,
+                          DefaultL2TunnelPolicy.class,
+                          L2Mode.class,
+                          MplsLabel.class,
+                          VlanId.class,
+                          ConnectPoint.class);
 
-        l2InitiationNextObjStore = srManager.storageService
-                .<String, NextObjective>consistentMapBuilder()
-                .withName("onos-l2initiation-nextobj-store")
-                .withSerializer(Serializer.using(l2TunnelKryo.build()))
-                .build();
+        l2InitiationNextObjStore = srManager.
+                storageService.
+                <String, NextObjective>consistentMapBuilder().
+                withName("onos-l2initiation-nextobj-store").
+                withSerializer(Serializer.using(l2TunnelKryo.build())).
+                build();
 
-        l2TerminationNextObjStore = srManager.storageService
-                .<String, NextObjective>consistentMapBuilder()
+        l2TerminationNextObjStore = srManager.storageService.
+                <String, NextObjective>consistentMapBuilder()
                 .withName("onos-l2termination-nextobj-store")
                 .withSerializer(Serializer.using(l2TunnelKryo.build()))
                 .build();
+
+        l2PolicyStore = srManager.storageService
+                .<String, DefaultL2TunnelPolicy>consistentMapBuilder()
+                .withName("onos-l2-policy-store")
+                .withSerializer(Serializer.using(l2TunnelKryo.build()))
+                .build();
+
+        l2TunnelStore = srManager.storageService
+                .<String, DefaultL2Tunnel>consistentMapBuilder()
+                .withName("onos-l2-tunnel-store")
+                .withSerializer(Serializer.using(l2TunnelKryo.build()))
+                .build();
+    }
+
+    /**
+     * Returns all L2 Policies.
+     *
+     * @return List of policies
+     */
+    public List<DefaultL2TunnelPolicy> getL2Policies() {
+
+        return l2PolicyStore
+                .values()
+                .stream()
+                .map(Versioned::value)
+                .map(DefaultL2TunnelPolicy::new)
+                .collect(Collectors.toList());
+
+    }
+
+    /**
+     * Returns all L2 Tunnels.
+     *
+     * @return List of tunnels.
+     */
+    public List<DefaultL2Tunnel> getL2Tunnels() {
+
+        return l2TunnelStore
+                .values()
+                .stream()
+                .map(Versioned::value)
+                .map(DefaultL2Tunnel::new)
+                .collect(Collectors.toList());
+
+    }
+
+    /**
+     * Processes a link removal. Finds affected pseudowires and rewires them.
+     * TODO: Make it also take into account failures of links that are used for pw
+     * traffic in the spine.
+     * @param link The link that failed
+     */
+    public void processLinkDown(Link link) {
+
+        List<DefaultL2Tunnel> tunnels = getL2Tunnels();
+        List<DefaultL2TunnelPolicy> policies = getL2Policies();
+
+        // determine affected pseudowires and update them at once
+        Set<DefaultL2TunnelDescription> pwToUpdate = tunnels
+                .stream()
+                .filter(tun -> tun.pathUsed().contains(link))
+                .map(l2Tunnel -> {
+                        DefaultL2TunnelPolicy policy = null;
+                        for (DefaultL2TunnelPolicy l2Policy : policies) {
+                            if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
+                                policy = l2Policy;
+                                break;
+                            }
+                        }
+
+                        return new DefaultL2TunnelDescription(l2Tunnel, policy);
+                })
+                .collect(Collectors.toSet());
+
+
+        log.info("Pseudowires affected by link failure : {}, rerouting them...", pwToUpdate);
+
+        // update all pseudowires
+        pwToUpdate.forEach(tun -> updatePw(tun, tun));
     }
 
     /**
@@ -132,93 +214,209 @@
      * @param event network config add event
      */
     public void processPwaasConfigAdded(NetworkConfigEvent event) {
-        log.info("Processing Pwaas CONFIG_ADDED");
+
+        log.info("Network event : Pseudowire configuration added!");
         PwaasConfig config = (PwaasConfig) event.config().get();
-        Set<DefaultL2TunnelDescription> pwToAdd = config.getPwIds()
+
+        // gather pseudowires
+        Set<DefaultL2TunnelDescription> pwToAdd = config
+                .getPwIds()
                 .stream()
                 .map(config::getPwDescription)
                 .collect(Collectors.toSet());
-        // We deploy all the pseudo wire deployed
+
+        // deploy pseudowires
         deploy(pwToAdd);
     }
 
     /**
+     * Returns the new vlan id for an ingress point of a
+     * pseudowire. For double tagged, it is the outer,
+     * For single tagged it is the single tag, and for
+     * inner it is None.
+     *
+     * @param ingressOuter vlanid of ingress outer
+     * @param ingressInner vlanid of ingress inner
+     * @param egressOuter  vlanid of egress outer
+     * @param egressInner  vlanid of egress inner
+     * @return returns the vlan id which will be installed at vlan table 1.
+     */
+    public VlanId determineEgressVlan(VlanId ingressOuter, VlanId ingressInner,
+                                      VlanId egressOuter, VlanId egressInner) {
+
+        // validity of vlan combinations was checked at verifyPseudowire
+        if (!(ingressOuter.equals(VlanId.NONE))) {
+            return egressOuter;
+        } else if (!(ingressInner.equals(VlanId.NONE))) {
+            return egressInner;
+        } else {
+            return VlanId.vlanId("None");
+        }
+    }
+
+    /**
+     * Adds a single pseudowire. This method can be called from cli commands
+     * without configration updates, thus it does not check for mastership
+     * of the ingress pseudowire device.
+     *
+     * @param pw The pseudowire
+     * @return
+     */
+    private Result deployPseudowire(DefaultL2TunnelDescription pw) {
+
+        Result result;
+        long l2TunnelId;
+
+        l2TunnelId = pw.l2Tunnel().tunnelId();
+
+        // The tunnel id cannot be 0.
+        if (l2TunnelId == 0) {
+            log.warn("Tunnel id id must be > 0");
+            return Result.ADDITION_ERROR;
+        }
+
+        // get path here, need to use the same for fwd and rev direction
+        List<Link> path = getPath(pw.l2TunnelPolicy().cP1(),
+                                  pw.l2TunnelPolicy().cP2());
+        if (path == null) {
+            log.info("Deploying process : No path between the connection points for pseudowire {}", l2TunnelId);
+            return WRONG_PARAMETERS;
+        }
+
+        pw.l2Tunnel().setPath(path);
+
+        // next hops for next objectives
+        Link fwdNextHop = path.get(0);
+        Link revNextHop = reverseLink(path.get(1));
+
+        log.info("Deploying process : Establishing forward direction for pseudowire {}", l2TunnelId);
+
+        // We establish the tunnel.
+        // result.nextId will be used in fwd
+        result = deployPseudoWireInit(pw.l2Tunnel(),
+                                      pw.l2TunnelPolicy().cP1(),
+                                      pw.l2TunnelPolicy().cP2(),
+                                      FWD,
+                                      fwdNextHop);
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in deploying pseudowire initiation for CP1");
+            return Result.ADDITION_ERROR;
+        }
+
+        VlanId egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP1OuterTag(),
+                                                pw.l2TunnelPolicy().cP1InnerTag(),
+                                                pw.l2TunnelPolicy().cP2OuterTag(),
+                                                pw.l2TunnelPolicy().cP2InnerTag());
+
+        // We create the policy.
+        result = deployPolicy(l2TunnelId,
+                              pw.l2TunnelPolicy().cP1(),
+                              pw.l2TunnelPolicy().cP1InnerTag(),
+                              pw.l2TunnelPolicy().cP1OuterTag(),
+                              egressVlan,
+                              result.nextId);
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in deploying pseudowire policy for CP1");
+            return Result.ADDITION_ERROR;
+        }
+
+        // We terminate the tunnel
+        result = deployPseudoWireTerm(pw.l2Tunnel(),
+                                       pw.l2TunnelPolicy().cP2(),
+                                       pw.l2TunnelPolicy().cP2OuterTag(),
+                                       FWD);
+
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in deploying pseudowire termination for CP1");
+            return Result.ADDITION_ERROR;
+
+        }
+
+        log.info("Deploying process : Establishing reverse direction for pseudowire {}", l2TunnelId);
+
+        // We establish the reverse tunnel.
+        result = deployPseudoWireInit(pw.l2Tunnel(),
+                                       pw.l2TunnelPolicy().cP2(),
+                                       pw.l2TunnelPolicy().cP1(),
+                                       REV,
+                                       revNextHop);
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in deploying pseudowire initiation for CP2");
+            return Result.ADDITION_ERROR;
+        }
+
+        egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP2OuterTag(),
+                                          pw.l2TunnelPolicy().cP2InnerTag(),
+                                          pw.l2TunnelPolicy().cP1OuterTag(),
+                                          pw.l2TunnelPolicy().cP1InnerTag());
+        result = deployPolicy(l2TunnelId,
+                               pw.l2TunnelPolicy().cP2(),
+                               pw.l2TunnelPolicy().cP2InnerTag(),
+                               pw.l2TunnelPolicy().cP2OuterTag(),
+                               egressVlan,
+                               result.nextId);
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in deploying policy for CP2");
+            return Result.ADDITION_ERROR;
+        }
+
+        result = deployPseudoWireTerm(pw.l2Tunnel(),
+                                       pw.l2TunnelPolicy().cP1(),
+                                       pw.l2TunnelPolicy().cP1OuterTag(),
+                                       REV);
+
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in deploying pseudowire termination for CP2");
+            return Result.ADDITION_ERROR;
+        }
+
+        log.info("Deploying process : Updating relevant information for pseudowire {}", l2TunnelId);
+
+        // Populate stores
+        l2TunnelStore.put(Long.toString(l2TunnelId), pw.l2Tunnel());
+        l2PolicyStore.put(Long.toString(l2TunnelId), pw.l2TunnelPolicy());
+
+        return Result.SUCCESS;
+    }
+
+    /**
      * To deploy a number of pseudo wires.
+     * <p>
+     * Called ONLY when configuration changes, thus the check
+     * for the mastership of the device.
+     * <p>
+     * Only the master of CP1 will deploy this pseudowire.
      *
      * @param pwToAdd the set of pseudo wires to add
      */
     private void deploy(Set<DefaultL2TunnelDescription> pwToAdd) {
+
         Result result;
-        long l2TunnelId;
+
         for (DefaultL2TunnelDescription currentL2Tunnel : pwToAdd) {
-            l2TunnelId = currentL2Tunnel.l2Tunnel().tunnelId();
-            // The tunnel id cannot be 0.
-            if (l2TunnelId == 0) {
-                log.warn("Tunnel id id must be > 0");
+
+            // only the master of CP1 will program this pseudowire
+            if (!srManager.isMasterOf(currentL2Tunnel.l2TunnelPolicy().cP1())) {
                 continue;
             }
-            // We do a sanity check of the pseudo wire.
-            result = verifyPseudoWire(currentL2Tunnel);
-            if (result != SUCCESS) {
-                continue;
+
+            log.info("Deploying pseudowire {}", currentL2Tunnel.l2Tunnel().tunnelId());
+
+            result = deployPseudowire(currentL2Tunnel);
+            switch (result) {
+                case WRONG_PARAMETERS:
+                    log.warn("Could not deploy pseudowire {}, wrong parameters!",
+                             currentL2Tunnel.l2Tunnel().tunnelId());
+                    break;
+                case ADDITION_ERROR:
+                    log.warn("Could not deploy pseudowire {}, error in populating rules!",
+                             currentL2Tunnel.l2Tunnel().tunnelId());
+                    break;
+                default:
+                    log.info("Pseudowire with {} succesfully deployed!",
+                             currentL2Tunnel.l2Tunnel().tunnelId());
+                    break;
             }
-            // We establish the tunnel.
-            result = deployPseudoWireInit(
-                    currentL2Tunnel.l2Tunnel(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    FWD
-            );
-            if (result != SUCCESS) {
-                continue;
-            }
-            // We create the policy.
-            result = deployPolicy(
-                    l2TunnelId,
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(),
-                    result.nextId
-            );
-            if (result != SUCCESS) {
-                continue;
-            }
-            // We terminate the tunnel
-            result = deployPseudoWireTerm(
-                    currentL2Tunnel.l2Tunnel(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
-                    FWD
-            );
-            if (result != SUCCESS) {
-                continue;
-            }
-            // We establish the reverse tunnel.
-            result = deployPseudoWireInit(
-                    currentL2Tunnel.l2Tunnel(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    REV
-            );
-            if (result != SUCCESS) {
-                continue;
-            }
-            result = deployPolicy(
-                    l2TunnelId,
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
-                    result.nextId
-            );
-            if (result != SUCCESS) {
-                continue;
-            }
-            deployPseudoWireTerm(
-                    currentL2Tunnel.l2Tunnel(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(),
-                    REV
-            );
         }
     }
 
@@ -228,46 +426,75 @@
      * @param event network config updated event
      */
     public void processPwaasConfigUpdated(NetworkConfigEvent event) {
-        log.info("Processing Pwaas CONFIG_UPDATED");
+
+        log.info("Pseudowire configuration updated.");
+
         // We retrieve the old pseudo wires.
         PwaasConfig prevConfig = (PwaasConfig) event.prevConfig().get();
         Set<Long> prevPws = prevConfig.getPwIds();
+
         // We retrieve the new pseudo wires.
         PwaasConfig config = (PwaasConfig) event.config().get();
         Set<Long> newPws = config.getPwIds();
+
         // We compute the pseudo wires to update.
         Set<Long> updPws = newPws.stream()
-                .filter(tunnelId -> prevPws.contains(tunnelId) &&
-                !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId)))
+                .filter(tunnelId -> prevPws.contains(tunnelId)
+                        && !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId)))
                 .collect(Collectors.toSet());
+
         // The pseudo wires to remove.
-        Set<DefaultL2TunnelDescription> pwToRemove = prevPws.stream()
-                .filter(tunnelId -> !newPws.contains(tunnelId))
+        Set<Long> rmvPWs = prevPws.stream()
+                .filter(tunnelId -> !newPws.contains(tunnelId)).collect(Collectors.toSet());
+
+        Set<DefaultL2TunnelDescription> pwToRemove = rmvPWs.stream()
                 .map(prevConfig::getPwDescription)
                 .collect(Collectors.toSet());
         tearDown(pwToRemove);
+
         // The pseudo wires to add.
-        Set<DefaultL2TunnelDescription> pwToAdd = newPws.stream()
+        Set<Long> addedPWs = newPws.stream()
                 .filter(tunnelId -> !prevPws.contains(tunnelId))
+                .collect(Collectors.toSet());
+        Set<DefaultL2TunnelDescription> pwToAdd = addedPWs.stream()
                 .map(config::getPwDescription)
                 .collect(Collectors.toSet());
         deploy(pwToAdd);
+
+
         // The pseudo wires to update.
-        updPws.forEach(tunnelId -> updatePw(
-                prevConfig.getPwDescription(tunnelId),
-                config.getPwDescription(tunnelId))
-        );
+        updPws.forEach(tunnelId -> updatePw(prevConfig.getPwDescription(tunnelId),
+                                            config.getPwDescription(tunnelId)));
+
+        log.info("Pseudowires removed : {}, Pseudowires updated : {}, Pseudowires added : {}", rmvPWs,
+                 updPws, addedPWs);
     }
 
     /**
      * Helper function to update a pw.
+     * <p>
+     * Called upon configuration changes that update existing pseudowires and
+     * when links fail. Checking of mastership for CP1 is mandatory because it is
+     * called in multiple instances for both cases.
+     * <p>
+     * Meant to call asynchronously for various events, thus this call can not block and need
+     * to perform asynchronous operations.
+     * <p>
+     * For this reason error checking is omitted.
      *
      * @param oldPw the pseudo wire to remove
-     * @param newPw the pseudo wirte to add
+     * @param newPw the pseudo wire to add
      */
-    private void updatePw(DefaultL2TunnelDescription oldPw,
-                          DefaultL2TunnelDescription newPw) {
+    public void updatePw(DefaultL2TunnelDescription oldPw, DefaultL2TunnelDescription newPw) {
         long tunnelId = oldPw.l2Tunnel().tunnelId();
+
+        // only the master of CP1 will update this pseudowire
+        if (!srManager.isMasterOf(oldPw.l2TunnelPolicy().cP1())) {
+            return;
+        }
+
+        log.info("Updating pseudowire {}", oldPw.l2Tunnel().tunnelId());
+
         // The async tasks to orchestrate the next and
         // forwarding update.
         CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
@@ -277,136 +504,165 @@
         CompletableFuture<ObjectiveError> fwdPwFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> revPwFuture = new CompletableFuture<>();
 
-
-        Result result = verifyPseudoWire(newPw);
-        if (result != SUCCESS) {
-            return;
-        }
         // First we remove both policy.
         log.debug("Start deleting fwd policy for {}", tunnelId);
-        deletePolicy(
-                tunnelId,
-                oldPw.l2TunnelPolicy().cP1(),
-                oldPw.l2TunnelPolicy().cP1InnerTag(),
-                oldPw.l2TunnelPolicy().cP1OuterTag(),
-                fwdInitNextFuture,
-                FWD
-        );
-        log.debug("Start deleting rev policy for {}", tunnelId);
-        deletePolicy(
-                tunnelId,
-                oldPw.l2TunnelPolicy().cP2(),
-                oldPw.l2TunnelPolicy().cP2InnerTag(),
-                oldPw.l2TunnelPolicy().cP2OuterTag(),
-                revInitNextFuture,
-                REV
-        );
+
+        // first delete all information from our stores
+        // we can not do it asynchronously
+        l2PolicyStore.remove(Long.toString(tunnelId));
+        l2TunnelStore.remove(Long.toString(tunnelId));
+
+        VlanId egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP1OuterTag(),
+                                                 oldPw.l2TunnelPolicy().cP1InnerTag(),
+                                                 oldPw.l2TunnelPolicy().cP2OuterTag(),
+                                                 oldPw.l2TunnelPolicy().cP2InnerTag());
+        deletePolicy(tunnelId,
+                      oldPw.l2TunnelPolicy().cP1(),
+                      oldPw.l2TunnelPolicy().cP1InnerTag(),
+                      oldPw.l2TunnelPolicy().cP1OuterTag(),
+                      egressVlan,
+                      fwdInitNextFuture,
+                      FWD);
+
+        log.debug("Update process : Start deleting rev policy for {}", tunnelId);
+
+        egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP2OuterTag(),
+                                          oldPw.l2TunnelPolicy().cP2InnerTag(),
+                                          oldPw.l2TunnelPolicy().cP1OuterTag(),
+                                          oldPw.l2TunnelPolicy().cP1InnerTag());
+        deletePolicy(tunnelId,
+                      oldPw.l2TunnelPolicy().cP2(),
+                      oldPw.l2TunnelPolicy().cP2InnerTag(),
+                      oldPw.l2TunnelPolicy().cP2OuterTag(),
+                      egressVlan, revInitNextFuture,
+                      REV);
+
         // Finally we remove both the tunnels.
         fwdInitNextFuture.thenAcceptAsync(status -> {
             if (status == null) {
-                log.debug("Fwd policy removed. Now remove fwd {} for {}", INITIATION, tunnelId);
-                tearDownPseudoWireInit(
-                        tunnelId,
-                        oldPw.l2TunnelPolicy().cP1(),
-                        fwdTermNextFuture,
-                        FWD
-                );
+                log.debug("Update process : Fwd policy removed. " +
+                                  "Now remove fwd {} for {}", INITIATION, tunnelId);
+                tearDownPseudoWireInit(tunnelId,
+                                        oldPw.l2TunnelPolicy().cP1(),
+                                        fwdTermNextFuture,
+                                        FWD);
             }
         });
         revInitNextFuture.thenAcceptAsync(status -> {
-           if (status == null) {
-               log.debug("Rev policy removed. Now remove rev {} for {}", INITIATION, tunnelId);
-               tearDownPseudoWireInit(
-                       tunnelId,
-                       oldPw.l2TunnelPolicy().cP2(),
-                       revTermNextFuture,
-                       REV
-               );
-
-           }
+            if (status == null) {
+                log.debug("Update process : Rev policy removed. " +
+                                  "Now remove rev {} for {}", INITIATION, tunnelId);
+                tearDownPseudoWireInit(tunnelId,
+                                        oldPw.l2TunnelPolicy().cP2(),
+                                        revTermNextFuture,
+                                        REV);
+            }
         });
         fwdTermNextFuture.thenAcceptAsync(status -> {
             if (status == null) {
-                log.debug("Fwd {} removed. Now remove fwd {} for {}", INITIATION, TERMINATION, tunnelId);
-                tearDownPseudoWireTerm(
-                        oldPw.l2Tunnel(),
-                        oldPw.l2TunnelPolicy().cP2(),
-                        fwdPwFuture,
-                        FWD
-                );
+                log.debug("Update process : Fwd {} removed. " +
+                                  "Now remove fwd {} for {}", INITIATION, TERMINATION, tunnelId);
+                tearDownPseudoWireTerm(oldPw.l2Tunnel(),
+                                        oldPw.l2TunnelPolicy().cP2(),
+                                        fwdPwFuture,
+                                        FWD);
             }
         });
         revTermNextFuture.thenAcceptAsync(status -> {
             if (status == null) {
-                log.debug("Rev {} removed. Now remove rev {} for {}", INITIATION, TERMINATION, tunnelId);
-                tearDownPseudoWireTerm(
-                        oldPw.l2Tunnel(),
-                        oldPw.l2TunnelPolicy().cP1(),
-                        revPwFuture,
-                        REV
-                );
+                log.debug("Update process : Rev {} removed. " +
+                                  "Now remove rev {} for {}", INITIATION, TERMINATION, tunnelId);
+                tearDownPseudoWireTerm(oldPw.l2Tunnel(),
+                                        oldPw.l2TunnelPolicy().cP1(),
+                                        revPwFuture,
+                                        REV);
             }
         });
-        // At the end we install the new pw.
+
+        // get path here, need to use the same for fwd and rev direction
+        List<Link> path = getPath(newPw.l2TunnelPolicy().cP1(),
+                                   newPw.l2TunnelPolicy().cP2());
+        if (path == null) {
+            log.info("Deploying process : " +
+                             "No path between the connection points for pseudowire {}", newPw.l2Tunnel().tunnelId());
+            return;
+        }
+
+        newPw.l2Tunnel().setPath(path);
+        // next hops for next objectives
+        Link fwdNextHop = path.get(0);
+        Link revNextHop = reverseLink(path.get(1));
+
+        // At the end we install the updated PW.
         fwdPwFuture.thenAcceptAsync(status -> {
             if (status == null) {
-                log.debug("Deploying new fwd pw for {}", tunnelId);
-                Result lamdaResult = deployPseudoWireInit(
-                        newPw.l2Tunnel(),
-                        newPw.l2TunnelPolicy().cP1(),
-                        newPw.l2TunnelPolicy().cP2(),
-                        FWD
-                );
+
+                // Upgrade stores and book keeping information, need to move this here
+                // cause this call is asynchronous.
+                l2PolicyStore.put(Long.toString(tunnelId), newPw.l2TunnelPolicy());
+                l2TunnelStore.put(Long.toString(tunnelId), newPw.l2Tunnel());
+
+                log.debug("Update process : Deploying new fwd pw for {}", tunnelId);
+                Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(),
+                                                           newPw.l2TunnelPolicy().cP1(),
+                                                           newPw.l2TunnelPolicy().cP2(),
+                                                           FWD,
+                                                           fwdNextHop);
                 if (lamdaResult != SUCCESS) {
                     return;
                 }
-                lamdaResult = deployPolicy(
-                        tunnelId,
-                        newPw.l2TunnelPolicy().cP1(),
-                        newPw.l2TunnelPolicy().cP1InnerTag(),
-                        newPw.l2TunnelPolicy().cP1OuterTag(),
-                        lamdaResult.nextId
-                );
+
+                VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP1OuterTag(),
+                                                           newPw.l2TunnelPolicy().cP1InnerTag(),
+                                                           newPw.l2TunnelPolicy().cP2OuterTag(),
+                                                           newPw.l2TunnelPolicy().cP2InnerTag());
+
+                lamdaResult = deployPolicy(tunnelId,
+                                            newPw.l2TunnelPolicy().cP1(),
+                                            newPw.l2TunnelPolicy().cP1InnerTag(),
+                                            newPw.l2TunnelPolicy().cP1OuterTag(),
+                                            egressVlanId,
+                                            lamdaResult.nextId);
                 if (lamdaResult != SUCCESS) {
                     return;
                 }
-                deployPseudoWireTerm(
-                        newPw.l2Tunnel(),
-                        newPw.l2TunnelPolicy().cP2(),
-                        newPw.l2TunnelPolicy().cP2OuterTag(),
-                        FWD
-                );
+                deployPseudoWireTerm(newPw.l2Tunnel(),
+                                      newPw.l2TunnelPolicy().cP2(),
+                                      newPw.l2TunnelPolicy().cP2OuterTag(),
+                                      FWD);
 
             }
         });
+
         revPwFuture.thenAcceptAsync(status -> {
             if (status == null) {
-                log.debug("Deploying new rev pw for {}", tunnelId);
-                Result lamdaResult = deployPseudoWireInit(
-                        newPw.l2Tunnel(),
-                        newPw.l2TunnelPolicy().cP2(),
-                        newPw.l2TunnelPolicy().cP1(),
-                        REV
-                );
+                log.debug("Update process : Deploying new rev pw for {}", tunnelId);
+                Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(),
+                                                           newPw.l2TunnelPolicy().cP2(),
+                                                           newPw.l2TunnelPolicy().cP1(),
+                                                           REV,
+                                                           revNextHop);
                 if (lamdaResult != SUCCESS) {
                     return;
                 }
-                lamdaResult = deployPolicy(
-                        tunnelId,
-                        newPw.l2TunnelPolicy().cP2(),
-                        newPw.l2TunnelPolicy().cP2InnerTag(),
-                        newPw.l2TunnelPolicy().cP2OuterTag(),
-                        lamdaResult.nextId
-                );
+
+                VlanId egressVlanId = determineEgressVlan(newPw.l2TunnelPolicy().cP2OuterTag(),
+                                                           newPw.l2TunnelPolicy().cP2InnerTag(),
+                                                           newPw.l2TunnelPolicy().cP1OuterTag(),
+                                                           newPw.l2TunnelPolicy().cP1InnerTag());
+                lamdaResult = deployPolicy(tunnelId,
+                                            newPw.l2TunnelPolicy().cP2(),
+                                            newPw.l2TunnelPolicy().cP2InnerTag(),
+                                            newPw.l2TunnelPolicy().cP2OuterTag(),
+                                            egressVlanId,
+                                            lamdaResult.nextId);
                 if (lamdaResult != SUCCESS) {
                     return;
                 }
-                deployPseudoWireTerm(
-                        newPw.l2Tunnel(),
-                        newPw.l2TunnelPolicy().cP1(),
-                        newPw.l2TunnelPolicy().cP1OuterTag(),
-                        REV
-                );
+                deployPseudoWireTerm(newPw.l2Tunnel(),
+                                      newPw.l2TunnelPolicy().cP1(),
+                                      newPw.l2TunnelPolicy().cP1OuterTag(),
+                                      REV);
             }
         });
     }
@@ -417,110 +673,165 @@
      * @param event network config removed event
      */
     public void processPwaasConfigRemoved(NetworkConfigEvent event) {
-        log.info("Processing Pwaas CONFIG_REMOVED");
+
+        log.info("Network event : Pseudowire configuration removed!");
         PwaasConfig config = (PwaasConfig) event.prevConfig().get();
-        Set<DefaultL2TunnelDescription> pwToRemove = config.getPwIds()
+
+        Set<DefaultL2TunnelDescription> pwToRemove = config
+                .getPwIds()
                 .stream()
                 .map(config::getPwDescription)
                 .collect(Collectors.toSet());
+
         // We teardown all the pseudo wire deployed
         tearDown(pwToRemove);
     }
 
     /**
-     * Helper function to handle the pw removal.
+     * Helper function for removing a single pseudowire.
+     * <p>
+     * No mastership of CP1 is checked, because it can be called from
+     * the CLI for removal of pseudowires.
      *
-     * @param pwToRemove the pseudo wires to remove
+     * @param l2TunnelId the id of the pseudowire to tear down
+     * @return Returns SUCCESS if no error is obeserved or an appropriate
+     * error on a failure
      */
-    private void tearDown(Set<DefaultL2TunnelDescription> pwToRemove) {
-        Result result;
-        long l2TunnelId;
-        // We remove all the pw in the configuration file.
-        for (DefaultL2TunnelDescription currentL2Tunnel : pwToRemove) {
-            l2TunnelId = currentL2Tunnel.l2Tunnel().tunnelId();
-            if (l2TunnelId == 0) {
-                log.warn("Tunnel id cannot be 0");
-                continue;
-            }
-            result = verifyPseudoWire(currentL2Tunnel);
-            if (result != SUCCESS) {
-                continue;
-            }
-            // First all we have to delete the policy.
-            deletePolicy(
-                    l2TunnelId,
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(),
-                    null,
-                    FWD
-            );
-            // Finally we will tear down the pseudo wire.
-            tearDownPseudoWireInit(
-                    l2TunnelId,
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    null,
-                    FWD
-            );
-            tearDownPseudoWireTerm(
-                    currentL2Tunnel.l2Tunnel(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    null,
-                    FWD
-            );
-            // We do the same operations on the reverse side.
-            deletePolicy(
-                    l2TunnelId,
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(),
-                    currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
-                    null,
-                    REV
-            );
-            tearDownPseudoWireInit(
-                    l2TunnelId,
-                    currentL2Tunnel.l2TunnelPolicy().cP2(),
-                    null,
-                    REV
-            );
-            tearDownPseudoWireTerm(
-                    currentL2Tunnel.l2Tunnel(),
-                    currentL2Tunnel.l2TunnelPolicy().cP1(),
-                    null,
-                    REV
-            );
+    public Result tearDownPseudowire(long l2TunnelId) {
+
+        CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
+        CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
+
+        CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
+        CompletableFuture<ObjectiveError> revTermNextFuture = new CompletableFuture<>();
+
+        if (l2TunnelId == 0) {
+            log.warn("Removal process : Tunnel id cannot be 0");
+            return Result.WRONG_PARAMETERS;
         }
 
+        // check existence of tunnels/policy in the store, if one is missing abort!
+        Versioned<DefaultL2Tunnel> l2TunnelVersioned = l2TunnelStore.get(Long.toString(l2TunnelId));
+        Versioned<DefaultL2TunnelPolicy> l2TunnelPolicyVersioned = l2PolicyStore.get(Long.toString(l2TunnelId));
+        if ((l2TunnelVersioned == null) || (l2TunnelPolicyVersioned == null)) {
+            log.warn("Removal process : Policy and/or tunnel missing for tunnel id {}", l2TunnelId);
+            return Result.REMOVAL_ERROR;
+        }
+
+        DefaultL2TunnelDescription pwToRemove = new DefaultL2TunnelDescription(l2TunnelVersioned.value(),
+                                                                               l2TunnelPolicyVersioned.value());
+
+        // remove the tunnels and the policies from the store
+        l2PolicyStore.remove(Long.toString(l2TunnelId));
+        l2TunnelStore.remove(Long.toString(l2TunnelId));
+
+        log.info("Removal process : Tearing down forward direction of pseudowire {}", l2TunnelId);
+
+        VlanId egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP1OuterTag(),
+                                                 pwToRemove.l2TunnelPolicy().cP1InnerTag(),
+                                                 pwToRemove.l2TunnelPolicy().cP2OuterTag(),
+                                                 pwToRemove.l2TunnelPolicy().cP2InnerTag());
+        deletePolicy(l2TunnelId,
+                      pwToRemove.l2TunnelPolicy().cP1(),
+                      pwToRemove.l2TunnelPolicy().cP1InnerTag(),
+                      pwToRemove.l2TunnelPolicy().cP1OuterTag(),
+                      egressVlan,
+                      fwdInitNextFuture,
+                      FWD);
+
+        fwdInitNextFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                // Finally we will tear down the pseudo wire.
+                tearDownPseudoWireInit(l2TunnelId,
+                                        pwToRemove.l2TunnelPolicy().cP1(),
+                                        fwdTermNextFuture,
+                                        FWD);
+            }
+        });
+
+        fwdTermNextFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
+                                        pwToRemove.l2TunnelPolicy().cP2(),
+                                        null,
+                                        FWD);
+            }
+        });
+
+        log.info("Removal process : Tearing down reverse direction of pseudowire {}", l2TunnelId);
+
+        egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP2OuterTag(),
+                                          pwToRemove.l2TunnelPolicy().cP2InnerTag(),
+                                          pwToRemove.l2TunnelPolicy().cP1OuterTag(),
+                                          pwToRemove.l2TunnelPolicy().cP1InnerTag());
+
+        // We do the same operations on the reverse side.
+        deletePolicy(l2TunnelId,
+                      pwToRemove.l2TunnelPolicy().cP2(),
+                      pwToRemove.l2TunnelPolicy().cP2InnerTag(),
+                      pwToRemove.l2TunnelPolicy().cP2OuterTag(),
+                      egressVlan,
+                      revInitNextFuture,
+                      REV);
+
+        revInitNextFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                tearDownPseudoWireInit(l2TunnelId,
+                                        pwToRemove.l2TunnelPolicy().cP2(),
+                                        revTermNextFuture,
+                                        REV);
+            }
+        });
+
+        revTermNextFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
+                                        pwToRemove.l2TunnelPolicy().cP1(),
+                                        null,
+                                        REV);
+            }
+        });
+
+        return Result.SUCCESS;
     }
 
     /**
-     * Helper method to verify the integrity of the pseudo wire.
+     * Helper function to handle the pw removal.
+     * <p>
+     * This method checks for the mastership of the device because it is
+     * used only from network configuration updates, thus we only want
+     * one instance only to program each pseudowire.
      *
-     * @param l2TunnelDescription the pseudo wire description
-     * @return the result of the check
+     * @param pwToRemove the pseudo wires to remove
      */
-    private Result verifyPseudoWire(DefaultL2TunnelDescription l2TunnelDescription) {
-        Result result;
-        DefaultL2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel();
-        DefaultL2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy();
-        result = verifyTunnel(l2Tunnel);
-        if (result != SUCCESS) {
-            log.warn("Tunnel {}: did not pass the validation", l2Tunnel.tunnelId());
-            return result;
-        }
-        result = verifyPolicy(
-                l2TunnelPolicy.isAllVlan(),
-                l2TunnelPolicy.cP1InnerTag(),
-                l2TunnelPolicy.cP1OuterTag(),
-                l2TunnelPolicy.cP2InnerTag(),
-                l2TunnelPolicy.cP2OuterTag()
-        );
-        if (result != SUCCESS) {
-            log.warn("Policy for tunnel {}: did not pass the validation", l2Tunnel.tunnelId());
-            return result;
-        }
+    public void tearDown(Set<DefaultL2TunnelDescription> pwToRemove) {
 
-        return SUCCESS;
+        Result result;
+
+        // We remove all the pw in the configuration file.
+        for (DefaultL2TunnelDescription currentL2Tunnel : pwToRemove) {
+
+            // only the master of CP1 will program this pseudowire
+            if (!srManager.isMasterOf(currentL2Tunnel.l2TunnelPolicy().cP1())) {
+                continue;
+            }
+
+            log.info("Removing pseudowire {}", currentL2Tunnel.l2Tunnel().tunnelId());
+
+            result = tearDownPseudowire(currentL2Tunnel.l2Tunnel().tunnelId());
+            switch (result) {
+                case WRONG_PARAMETERS:
+                    log.warn("Error in supplied parameters for the pseudowire removal with tunnel id {}!",
+                             currentL2Tunnel.l2Tunnel().tunnelId());
+                    break;
+                case REMOVAL_ERROR:
+                    log.warn("Error in pseudowire removal with tunnel id {}!", currentL2Tunnel.l2Tunnel().tunnelId());
+                    break;
+                default:
+                    log.warn("Pseudowire with tunnel id {} was removed successfully",
+                             currentL2Tunnel.l2Tunnel().tunnelId());
+            }
+        }
     }
 
     /**
@@ -528,54 +839,48 @@
      * create the filtering and forwarding objectives related
      * to the initiation and termination.
      *
-     * @param tunnelId the tunnel id
-     * @param ingress the ingress point
+     * @param tunnelId     the tunnel id
+     * @param ingress      the ingress point
      * @param ingressInner the ingress inner tag
      * @param ingressOuter the ingress outer tag
-     * @param nextId the next objective id
+     * @param nextId       the next objective id
+     * @param egressVlan   Vlan-id to set, depends on ingress vlan
+     *                     combinations. For example, if pw is double tagged
+     *                     then this is the value of the outer vlan, if single
+     *                     tagged then it is the new value of the single tag.
+     *                     Should be None for untagged traffic.
      * @return the result of the operation
      */
-    private Result deployPolicy(long tunnelId,
-                                ConnectPoint ingress,
-                                VlanId ingressInner,
-                                VlanId ingressOuter,
-                                int nextId) {
-        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
-            log.info("Abort creation of policy for tunnel {}: I am not the master", tunnelId);
-            return SUCCESS;
-        }
+    private Result deployPolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner,
+                                VlanId ingressOuter, VlanId egressVlan, int nextId) {
+
         List<Objective> objectives = Lists.newArrayList();
         // We create the forwarding objective for supporting
         // the l2 tunnel.
-        ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(
-                tunnelId,
-                ingress.port(),
-                nextId
-        );
+        ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
         // We create and add objective context.
-        ObjectiveContext context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("FwdObj for tunnel {} populated", tunnelId),
-                (objective, error)
-                        -> log.warn("Failed to populate fwdrObj for tunnel {}", tunnelId, error));
+        ObjectiveContext context = new DefaultObjectiveContext((objective) ->
+                                                                log.debug("FwdObj for tunnel {} populated", tunnelId),
+                                                               (objective, error) ->
+                                                                log.warn("Failed to populate fwdrObj " +
+                                                                                 "for tunnel {}", tunnelId, error));
         objectives.add(fwdBuilder.add(context));
+
         // We create the filtering objective to define the
         // permit traffic in the switch
-        FilteringObjective.Builder filtBuilder = createFiltObjective(
-                ingress.port(),
-                ingressInner,
-                ingressOuter
-        );
+        FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
+
         // We add the metadata.
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
-                .setTunnelId(tunnelId);
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment
+                .builder()
+                .setTunnelId(tunnelId)
+                .setVlanId(egressVlan);
         filtBuilder.withMeta(treatment.build());
+
         // We create and add objective context.
-        context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("FilterObj for tunnel {} populated", tunnelId),
-                (objective, error)
-                        -> log.warn("Failed to populate filterObj for tunnel {}", tunnelId, error));
+        context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for tunnel {} populated", tunnelId),
+                                              (objective, error) -> log.warn("Failed to populate filterObj for " +
+                                                                                     "tunnel {}", tunnelId, error));
         objectives.add(filtBuilder.add(context));
 
         for (Objective objective : objectives) {
@@ -591,73 +896,32 @@
     }
 
     /**
-     * Helper method to verify if the policy is whether or not
-     * supported.
-     *
-     * @param isAllVlan all vlan mode
-     * @param ingressInner the ingress inner tag
-     * @param ingressOuter the ingress outer tag
-     * @param egressInner the egress inner tag
-     * @param egressOuter the egress outer tag
-     * @return the result of verification
-     */
-    private Result verifyPolicy(boolean isAllVlan,
-                                VlanId ingressInner,
-                                VlanId ingressOuter,
-                                VlanId egressInner,
-                                VlanId egressOuter) {
-        // AllVlan mode is not supported yet.
-        if (isAllVlan) {
-            log.warn("AllVlan not supported yet");
-            return UNSUPPORTED;
-        }
-        // The vlan tags for cP1 and cP2 have to be different from
-        // vlan none.
-        if (ingressInner.equals(VlanId.NONE) ||
-                ingressOuter.equals(VlanId.NONE) ||
-                egressInner.equals(VlanId.NONE) ||
-                egressOuter.equals(VlanId.NONE)) {
-            log.warn("The vlan tags for the connect point have to be" +
-                             "different from vlan none");
-            return WRONG_PARAMETERS;
-        }
-        return SUCCESS;
-    }
-
-    /**
      * Handles the tunnel establishment which consists in
      * create the next objectives related to the initiation.
      *
-     * @param l2Tunnel the tunnel to deploy
-     * @param ingress the ingress connect point
-     * @param egress the egress connect point
+     * @param l2Tunnel  the tunnel to deploy
+     * @param ingress   the ingress connect point
+     * @param egress    the egress connect point
      * @param direction the direction of the pw
      * @return the result of the operation
      */
-    private Result deployPseudoWireInit(DefaultL2Tunnel l2Tunnel,
-                                        ConnectPoint ingress,
-                                        ConnectPoint egress,
-                                        Direction direction) {
-        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
-            log.info("Abort initiation of tunnel {}: I am not the master", l2Tunnel.tunnelId());
-            return SUCCESS;
-        }
-        // We need at least a path between ingress and egress.
-        Link nextHop = getNextHop(ingress, egress);
+    private Result deployPseudoWireInit(DefaultL2Tunnel l2Tunnel, ConnectPoint ingress,
+                                        ConnectPoint egress, Direction direction, Link nextHop) {
+
         if (nextHop == null) {
-            log.warn("No path between ingress and egress");
+            log.warn("No path between ingress and egress cps for tunnel {}", l2Tunnel.tunnelId());
             return WRONG_PARAMETERS;
         }
+
         // We create the next objective without the metadata
         // context and id. We check if it already exists in the
         // store. If not we store as it is in the store.
-        NextObjective.Builder nextObjectiveBuilder = createNextObjective(
-                INITIATION,
-                nextHop.src(),
-                nextHop.dst(),
-                l2Tunnel,
-                egress.deviceId()
-        );
+        NextObjective.Builder nextObjectiveBuilder = createNextObjective(INITIATION,
+                                                                         nextHop.src(),
+                                                                         nextHop.dst(),
+                                                                         l2Tunnel,
+                                                                         egress.deviceId());
+
         if (nextObjectiveBuilder == null) {
             return INTERNAL_ERROR;
         }
@@ -676,19 +940,18 @@
         nextObjectiveBuilder.withId(nextId);
         String key = generateKey(l2Tunnel.tunnelId(), direction);
         l2InitiationNextObjStore.put(key, nextObjectiveBuilder.add());
-        ObjectiveContext context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("Initiation l2 tunnel rule for {} populated",
-                                     l2Tunnel.tunnelId()),
-                (objective, error)
-                        -> log.warn("Failed to populate Initiation l2 tunnel rule for {}: {}",
-                                    l2Tunnel.tunnelId(), error));
+        ObjectiveContext context = new DefaultObjectiveContext((objective) ->
+                                                                 log.debug("Initiation l2 tunnel rule " +
+                                                                                   "for {} populated",
+                                                                           l2Tunnel.tunnelId()),
+                                                               (objective, error) ->
+                                                                       log.warn("Failed to populate Initiation " +
+                                                                                        "l2 tunnel rule for {}: {}",
+                                                                                l2Tunnel.tunnelId(), error));
         NextObjective nextObjective = nextObjectiveBuilder.add(context);
         srManager.flowObjectiveService.next(ingress.deviceId(), nextObjective);
         log.debug("Initiation next objective for {} not found. Creating new NextObj with id={}",
-                  l2Tunnel.tunnelId(),
-                  nextObjective.id()
-        );
+                  l2Tunnel.tunnelId(), nextObjective.id());
         Result result = SUCCESS;
         result.nextId = nextObjective.id();
         return result;
@@ -698,30 +961,18 @@
      * Handles the tunnel termination, which consists in the creation
      * of a forwarding objective and a next objective.
      *
-     * @param l2Tunnel the tunnel to terminate
-     * @param egress the egress point
+     * @param l2Tunnel   the tunnel to terminate
+     * @param egress     the egress point
      * @param egressVlan the expected vlan at egress
-     * @param direction the direction
+     * @param direction  the direction
      * @return the result of the operation
      */
-    private Result deployPseudoWireTerm(DefaultL2Tunnel l2Tunnel,
-                                        ConnectPoint egress,
-                                        VlanId egressVlan,
-                                        Direction direction) {
+    private Result deployPseudoWireTerm(DefaultL2Tunnel l2Tunnel, ConnectPoint egress,
+                                        VlanId egressVlan, Direction direction) {
+
         // We create the group relative to the termination.
-        // It's fine to abort the termination if we are
-        // not the master.
-        if (!srManager.mastershipService.isLocalMaster(egress.deviceId())) {
-            log.info("Abort termination of tunnel {}: I am not the master", l2Tunnel.tunnelId());
-            return SUCCESS;
-        }
-        NextObjective.Builder nextObjectiveBuilder = createNextObjective(
-                TERMINATION,
-                egress,
-                null,
-                null,
-                egress.deviceId()
-        );
+        NextObjective.Builder nextObjectiveBuilder = createNextObjective(TERMINATION, egress, null,
+                                                                         null, egress.deviceId());
         if (nextObjectiveBuilder == null) {
             return INTERNAL_ERROR;
         }
@@ -738,74 +989,47 @@
         nextObjectiveBuilder.withId(nextId);
         String key = generateKey(l2Tunnel.tunnelId(), direction);
         l2TerminationNextObjStore.put(key, nextObjectiveBuilder.add());
-        ObjectiveContext context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("Termination l2 tunnel rule for {} populated",
-                                     l2Tunnel.tunnelId()),
-                (objective, error)
-                        -> log.warn("Failed to populate termination l2 tunnel rule for {}: {}",
-                                    l2Tunnel.tunnelId(), error));
+        ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("Termination l2 tunnel rule " +
+                                                                                        "for {} populated",
+                                                                                        l2Tunnel.tunnelId()),
+                                                               (objective, error) -> log.warn("Failed to populate " +
+                                                                                              "termination l2 tunnel " +
+                                                                                              "rule for {}: {}",
+                                                                                              l2Tunnel.tunnelId(),
+                                                                                              error));
         NextObjective nextObjective = nextObjectiveBuilder.add(context);
         srManager.flowObjectiveService.next(egress.deviceId(), nextObjective);
         log.debug("Termination next objective for {} not found. Creating new NextObj with id={}",
-                  l2Tunnel.tunnelId(),
-                  nextObjective.id()
-        );
+                  l2Tunnel.tunnelId(), nextObjective.id());
+
         // We create the flow relative to the termination.
-        ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(
-                l2Tunnel.pwLabel(),
-                l2Tunnel.tunnelId(),
-                egress.port(),
-                nextObjective.id()
-        );
-        context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("FwdObj for tunnel termination {} populated",
-                                     l2Tunnel.tunnelId()),
-                (objective, error)
-                        -> log.warn("Failed to populate fwdrObj for tunnel termination {}",
-                                    l2Tunnel.tunnelId(), error));
+        ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(), l2Tunnel.tunnelId(),
+                                                                        egress.port(), nextObjective.id());
+        context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for tunnel termination {} populated",
+                                                                       l2Tunnel.tunnelId()),
+                                              (objective, error) -> log.warn("Failed to populate fwdrObj" +
+                                                                             " for tunnel termination {}",
+                                                                             l2Tunnel.tunnelId(), error));
         srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.add(context));
-        log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}", nextId, l2Tunnel.tunnelId());
+        log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}",
+                  nextId, l2Tunnel.tunnelId());
         return SUCCESS;
 
     }
 
     /**
-     * Helper method to verify if the tunnel is whether or not
-     * supported.
-     *
-     * @param l2Tunnel the tunnel to verify
-     * @return the result of the verification
-     */
-    private Result verifyTunnel(DefaultL2Tunnel l2Tunnel) {
-        // Service delimiting tag not supported yet.
-        if (!l2Tunnel.sdTag().equals(VlanId.NONE)) {
-            log.warn("Service delimiting tag not supported yet");
-            return UNSUPPORTED;
-        }
-        // Tag mode not supported yet.
-        if (l2Tunnel.pwMode() == TAGGED) {
-            log.warn("Tagged mode not supported yet");
-            return UNSUPPORTED;
-        }
-        // Raw mode without service delimiting tag
-        // is the only mode supported for now.
-        return SUCCESS;
-    }
-
-    /**
      * Creates the filtering objective according to a given policy.
      *
-     * @param inPort the in port
+     * @param inPort   the in port
      * @param innerTag the inner vlan tag
      * @param outerTag the outer vlan tag
      * @return the filtering objective
      */
-    private FilteringObjective.Builder createFiltObjective(PortNumber inPort,
-                                                           VlanId innerTag,
-                                                           VlanId outerTag) {
-        return DefaultFilteringObjective.builder()
+    private FilteringObjective.Builder createFiltObjective(PortNumber inPort, VlanId innerTag, VlanId outerTag) {
+
+        log.info("Creating filtering objective for vlans {} / {}", outerTag, innerTag);
+        return DefaultFilteringObjective
+                .builder()
                 .withKey(Criteria.matchInPort(inPort))
                 .addCondition(Criteria.matchInnerVlanId(innerTag))
                 .addCondition(Criteria.matchVlanId(outerTag))
@@ -817,20 +1041,17 @@
     /**
      * Creates the forwarding objective for the termination.
      *
-     * @param pwLabel the pseudo wire label
-     * @param tunnelId the tunnel id
+     * @param pwLabel    the pseudo wire label
+     * @param tunnelId   the tunnel id
      * @param egressPort the egress port
-     * @param nextId the next step
+     * @param nextId     the next step
      * @return the forwarding objective to support the termination
      */
-    private ForwardingObjective.Builder createTermFwdObjective(MplsLabel pwLabel,
-                                                               long tunnelId,
-                                                               PortNumber egressPort,
-                                                               int nextId) {
-        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector
-                .builder();
-        TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment
-                .builder();
+    private ForwardingObjective.Builder createTermFwdObjective(MplsLabel pwLabel, long tunnelId,
+                                                               PortNumber egressPort, int nextId) {
+
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder trafficTreatment = DefaultTrafficTreatment.builder();
         // The flow has to match on the pw label and bos
         trafficSelector.matchEthType(Ethernet.MPLS_UNICAST);
         trafficSelector.matchMplsLabel(pwLabel);
@@ -843,7 +1064,8 @@
         trafficTreatment.setTunnelId(tunnelId);
         trafficTreatment.setOutput(egressPort);
 
-        return DefaultForwardingObjective.builder()
+        return DefaultForwardingObjective
+                .builder()
                 .fromApp(srManager.appId())
                 .makePermanent()
                 .nextStep(nextId)
@@ -857,21 +1079,21 @@
      * Creates the forwarding objective for the initiation.
      *
      * @param tunnelId the tunnel id
-     * @param inPort the input port
-     * @param nextId the next step
+     * @param inPort   the input port
+     * @param nextId   the next step
      * @return the forwarding objective to support the initiation.
      */
-    private ForwardingObjective.Builder createInitFwdObjective(long tunnelId,
-                                                               PortNumber inPort,
-                                                               int nextId) {
-        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector
-                .builder();
+    private ForwardingObjective.Builder createInitFwdObjective(long tunnelId, PortNumber inPort, int nextId) {
+
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
         // The flow has to match on the mpls logical
         // port and the tunnel id.
         trafficSelector.matchTunnelId(tunnelId);
         trafficSelector.matchInPort(inPort);
 
-        return DefaultForwardingObjective.builder()
+        return DefaultForwardingObjective
+                .builder()
                 .fromApp(srManager.appId())
                 .makePermanent()
                 .nextStep(nextId)
@@ -888,16 +1110,14 @@
      * the same next objective for different tunnels.
      *
      * @param pipeline the pipeline to support
-     * @param srcCp the source port
-     * @param dstCp the destination port
+     * @param srcCp    the source port
+     * @param dstCp    the destination port
      * @param l2Tunnel the tunnel to support
      * @param egressId the egress device id
      * @return the next objective to support the pipeline
      */
-    private NextObjective.Builder createNextObjective(Pipeline pipeline,
-                                                      ConnectPoint srcCp,
-                                                      ConnectPoint dstCp,
-                                                      DefaultL2Tunnel l2Tunnel,
+    private NextObjective.Builder createNextObjective(Pipeline pipeline, ConnectPoint srcCp,
+                                                      ConnectPoint dstCp,  DefaultL2Tunnel l2Tunnel,
                                                       DeviceId egressId) {
         NextObjective.Builder nextObjBuilder;
         TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
@@ -924,16 +1144,17 @@
                 treatmentBuilder.copyTtlOut();
             }
             // We retrieve the sr label from the config
+            // specific for pseudowire traffic
             // using the egress leaf device id.
             MplsLabel srLabel;
             try {
-                 srLabel = MplsLabel.mplsLabel(
-                         srManager.deviceConfiguration().getIPv4SegmentId(egressId)
-                 );
+                srLabel = MplsLabel.mplsLabel(srManager.deviceConfiguration().getPWRoutingLabel(egressId));
+
             } catch (DeviceConfigNotFoundException e) {
-                log.warn("Sr label not configured");
+                log.warn("Sr label for pw traffic not configured");
                 return null;
             }
+
             treatmentBuilder.pushMpls();
             treatmentBuilder.setMpls(srLabel);
             treatmentBuilder.setMplsBos(false);
@@ -941,9 +1162,7 @@
             // We have to rewrite the src and dst mac address.
             MacAddress ingressMac;
             try {
-                ingressMac = srManager
-                        .deviceConfiguration()
-                        .getDeviceMac(srcCp.deviceId());
+                ingressMac = srManager.deviceConfiguration().getDeviceMac(srcCp.deviceId());
             } catch (DeviceConfigNotFoundException e) {
                 log.warn("Was not able to find the ingress mac");
                 return null;
@@ -951,9 +1170,7 @@
             treatmentBuilder.setEthSrc(ingressMac);
             MacAddress neighborMac;
             try {
-                neighborMac = srManager
-                        .deviceConfiguration()
-                        .getDeviceMac(dstCp.deviceId());
+                neighborMac = srManager.deviceConfiguration().getDeviceMac(dstCp.deviceId());
             } catch (DeviceConfigNotFoundException e) {
                 log.warn("Was not able to find the neighbor mac");
                 return null;
@@ -973,54 +1190,64 @@
     }
 
     /**
-     * Returns the next hop.
+     * Reverses a link.
      *
-     * @param srcCp the ingress connect point
-     * @param dstCp the egress connect point
-     * @return the next hop
+     * @param link
+     * @return The reversed link
      */
-    private Link getNextHop(ConnectPoint srcCp, ConnectPoint dstCp) {
-        // We retrieve a set of disjoint paths.
-        Set<DisjointPath> paths = srManager.pathService.getDisjointPaths(
-                srcCp.elementId(),
-                dstCp.elementId()
-        );
-        // We randmly pick a path.
+    private Link reverseLink(Link link) {
+
+        DefaultLink.Builder linkBuilder = DefaultLink.builder();
+
+        linkBuilder.src(link.dst());
+        linkBuilder.dst(link.src());
+        linkBuilder.type(link.type());
+        linkBuilder.providerId(link.providerId());
+
+        return linkBuilder.build();
+    }
+
+    /**
+     * Returns the path betwwen two connect points.
+     *
+     * @param srcCp
+     * @param dstCp
+     * @return The path
+     */
+    private List<Link> getPath(ConnectPoint srcCp, ConnectPoint dstCp) {
+
+        /* We retrieve a set of disjoint paths.
+        * We perform that in case of a link failure, what happens
+        * if the PathService gets the link notification AFTER us and
+        * has not updated the paths?
+        */
+        Set<DisjointPath> paths = srManager
+                .pathService
+                .getDisjointPaths(srcCp.elementId(), dstCp.elementId());
+
+        // We randomly pick a path.
         if (paths.isEmpty()) {
             return null;
         }
         int size = paths.size();
         int index = RandomUtils.nextInt(0, size);
-        // We verify if the path is ok and there is not
-        // a misconfiguration.
-        List<Link> links = Iterables.get(paths, index).links();
-        checkState(links.size() == 2, WRONG_TOPOLOGY, links);
-        return links.get(0);
+
+        return Iterables.get(paths, index).links();
     }
 
     /**
      * Deletes a given policy using the parameter supplied.
      *
-     * @param tunnelId the tunnel id
-     * @param ingress the ingress point
+     * @param tunnelId     the tunnel id
+     * @param ingress      the ingress point
      * @param ingressInner the ingress inner vlan id
      * @param ingressOuter the ingress outer vlan id
-     * @param future to perform the async operation
-     * @param direction the direction: forward or reverse
+     * @param future       to perform the async operation
+     * @param direction    the direction: forward or reverse
      */
-    private void deletePolicy(long tunnelId,
-                              ConnectPoint ingress,
-                              VlanId ingressInner,
-                              VlanId ingressOuter,
-                              CompletableFuture<ObjectiveError> future,
-                              Direction direction) {
-        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
-            log.info("Abort delete of policy for tunnel {}: I am not the master", tunnelId);
-            if (future != null) {
-                future.complete(null);
-            }
-            return;
-        }
+    private void deletePolicy(long tunnelId, ConnectPoint ingress, VlanId ingressInner, VlanId ingressOuter,
+                              VlanId egressVlan, CompletableFuture<ObjectiveError> future, Direction direction) {
+
         String key = generateKey(tunnelId, direction);
         if (!l2InitiationNextObjStore.containsKey(key)) {
             log.warn("Abort delete of policy for tunnel {}: next does not exist in the store", tunnelId);
@@ -1033,11 +1260,7 @@
         int nextId = nextObjective.id();
         List<Objective> objectives = Lists.newArrayList();
         // We create the forwarding objective.
-        ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(
-                tunnelId,
-                ingress.port(),
-                nextId
-        );
+        ForwardingObjective.Builder fwdBuilder = createInitFwdObjective(tunnelId, ingress.port(), nextId);
         ObjectiveContext context = new ObjectiveContext() {
             @Override
             public void onSuccess(Objective objective) {
@@ -1058,19 +1281,16 @@
         objectives.add(fwdBuilder.remove(context));
         // We create the filtering objective to define the
         // permit traffic in the switch
-        FilteringObjective.Builder filtBuilder = createFiltObjective(
-                ingress.port(),
-                ingressInner,
-                ingressOuter
-        );
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
-                .setTunnelId(tunnelId);
+        FilteringObjective.Builder filtBuilder = createFiltObjective(ingress.port(), ingressInner, ingressOuter);
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment
+                .builder()
+                .setTunnelId(tunnelId)
+                .setVlanId(egressVlan);
         filtBuilder.withMeta(treatment.build());
-        context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("FilterObj for policy {} revoked", tunnelId),
-                (objective, error)
-                        -> log.warn("Failed to revoke filterObj for policy {}", tunnelId, error));
+        context = new DefaultObjectiveContext((objective) -> log.debug("FilterObj for policy {} revoked", tunnelId),
+                                              (objective, error) ->
+                                                      log.warn("Failed to revoke filterObj for policy {}",
+                                                               tunnelId, error));
         objectives.add(filtBuilder.remove(context));
 
         for (Objective objective : objectives) {
@@ -1086,22 +1306,14 @@
      * Deletes the pseudo wire initiation.
      *
      * @param l2TunnelId the tunnel id
-     * @param ingress the ingress connect point
-     * @param future to perform an async operation
-     * @param direction the direction: reverse of forward
+     * @param ingress    the ingress connect point
+     * @param future     to perform an async operation
+     * @param direction  the direction: reverse of forward
      */
-    private void tearDownPseudoWireInit(long l2TunnelId,
-                                        ConnectPoint ingress,
-                                        CompletableFuture<ObjectiveError> future,
-                                        Direction direction) {
+    private void tearDownPseudoWireInit(long l2TunnelId, ConnectPoint ingress,
+                                        CompletableFuture<ObjectiveError> future, Direction direction) {
+
         String key = generateKey(l2TunnelId, direction);
-        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
-            log.info("Abort delete of {} for {}: I am not the master", INITIATION, key);
-            if (future != null) {
-                future.complete(null);
-            }
-            return;
-        }
         if (!l2InitiationNextObjStore.containsKey(key)) {
             log.info("Abort delete of {} for {}: next does not exist in the store", INITIATION, key);
             if (future != null) {
@@ -1110,6 +1322,10 @@
             return;
         }
         NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
+        // un-comment in case you want to delete groups used by the pw
+        // however, this will break the update of pseudowires cause the L2 interface group can
+        // not be deleted (it is referenced by other groups)
+        /*
         ObjectiveContext context = new ObjectiveContext() {
             @Override
             public void onSuccess(Objective objective) {
@@ -1127,34 +1343,27 @@
                 }
             }
         };
-        srManager.flowObjectiveService
-                .next(ingress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
+        srManager.flowObjectiveService.next(ingress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
+        */
+
+        future.complete(null);
         l2InitiationNextObjStore.remove(key);
     }
 
     /**
      * Deletes the pseudo wire termination.
      *
-     * @param l2Tunnel the tunnel
-     * @param egress the egress connect point
-     * @param future the async task
+     * @param l2Tunnel  the tunnel
+     * @param egress    the egress connect point
+     * @param future    the async task
      * @param direction the direction of the tunnel
      */
     private void tearDownPseudoWireTerm(DefaultL2Tunnel l2Tunnel,
                                         ConnectPoint egress,
                                         CompletableFuture<ObjectiveError> future,
                                         Direction direction) {
-        /*
-         * We verify the mastership for the termination.
-         */
+
         String key = generateKey(l2Tunnel.tunnelId(), direction);
-        if (!srManager.mastershipService.isLocalMaster(egress.deviceId())) {
-            log.info("Abort delete of {} for {}: I am not the master", TERMINATION, key);
-            if (future != null) {
-                future.complete(null);
-            }
-            return;
-        }
         if (!l2TerminationNextObjStore.containsKey(key)) {
             log.info("Abort delete of {} for {}: next does not exist in the store", TERMINATION, key);
             if (future != null) {
@@ -1163,20 +1372,24 @@
             return;
         }
         NextObjective nextObjective = l2TerminationNextObjStore.get(key).value();
-        ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(
-                l2Tunnel.pwLabel(),
-                l2Tunnel.tunnelId(),
-                egress.port(),
-                nextObjective.id()
-        );
-        ObjectiveContext context = new DefaultObjectiveContext(
-                (objective)
-                        -> log.debug("FwdObj for {} {} removed", TERMINATION, l2Tunnel.tunnelId()),
-                (objective, error)
-                        -> log.warn("Failed to remove fwdObj for {} {}", TERMINATION, l2Tunnel.tunnelId(),
-                                    error));
+        ForwardingObjective.Builder fwdBuilder = createTermFwdObjective(l2Tunnel.pwLabel(),
+                                                                        l2Tunnel.tunnelId(),
+                                                                        egress.port(),
+                                                                        nextObjective.id());
+        ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for {} {} removed",
+                                                                                        TERMINATION,
+                                                                                        l2Tunnel.tunnelId()),
+                                                               (objective, error) ->
+                                                                       log.warn("Failed to remove fwdObj for {} {}",
+                                                                                TERMINATION,
+                                                                                l2Tunnel.tunnelId(),
+                                                                                error));
         srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.remove(context));
 
+        // un-comment in case you want to delete groups used by the pw
+        // however, this will break the update of pseudowires cause the L2 interface group can
+        // not be deleted (it is referenced by other groups)
+        /*
         context = new ObjectiveContext() {
             @Override
             public void onSuccess(Objective objective) {
@@ -1194,15 +1407,17 @@
                 }
             }
         };
-        srManager.flowObjectiveService
-                .next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
+        srManager.flowObjectiveService.next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
+        */
+
+        future.complete(null);
         l2TerminationNextObjStore.remove(key);
     }
 
     /**
      * Utilities to generate pw key.
      *
-     * @param tunnelId the tunnel id
+     * @param tunnelId  the tunnel id
      * @param direction the direction of the pw
      * @return the key of the store
      */
@@ -1217,38 +1432,46 @@
         /**
          * The initiation pipeline.
          */
-        INITIATION,
-        /**
+        INITIATION, /**
          * The termination pipeline.
          */
         TERMINATION
     }
 
     /**
-     * Enum helper to carry the outcomes of an operation.
+     * Enum helper to carry results of various operations.
      */
     public enum Result {
         /**
-         * Happy ending scenario it has been created.
+         * Happy ending scenario.
          */
-        SUCCESS(0, "It has been Created"),
+        SUCCESS(0, "No error occurred"),
+
         /**
          * We have problems with the supplied parameters.
          */
         WRONG_PARAMETERS(1, "Wrong parameters"),
-        /**
-         * It already exists.
-         */
-        ID_EXISTS(2, "The id already exists"),
+
         /**
          * We have an internal error during the deployment
-         * phase.
+         * or removal phase.
          */
         INTERNAL_ERROR(3, "Internal error"),
+
         /**
-         * The operation is not supported.
+         *
          */
-        UNSUPPORTED(4, "Unsupported");
+        REMOVAL_ERROR(5, "Can not remove pseudowire from network configuration"),
+
+        /**
+         *
+         */
+        ADDITION_ERROR(6, "Can not add pseudowire in network configuration"),
+
+        /**
+         *
+         */
+        CONFIG_NOT_FOUND(7, "Can not find configuration class for pseudowires");
 
         private final int code;
         private final String description;
@@ -1276,8 +1499,7 @@
         /**
          * The forward direction of the pseudo wire.
          */
-        FWD,
-        /**
+        FWD, /**
          * The reverse direction of the pseudo wire.
          */
         REV