Pseudowire enhancements :

  - Fix bug for transporting l-s-s pws tagged.
  - Refactored and cleaned code.
  - Refactored cli commands.
  - Fixed bug in bulk rest api when the same pseudowires were tried
    to be added.

Change-Id: I28ded776266a08110922b93a8b330d0b343d470d
diff --git a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 0e4373b..93665ec 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -126,6 +126,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.HashSet;
@@ -557,8 +558,8 @@
     }
 
     @Override
-    public Set<L2TunnelDescription> getL2TunnelDescriptions() {
-        return l2TunnelHandler.getL2Descriptions();
+    public Set<L2TunnelDescription> getL2TunnelDescriptions(boolean pending) {
+        return l2TunnelHandler.getL2Descriptions(pending);
     }
 
     @Override
@@ -574,16 +575,22 @@
     @Override
     public L2TunnelHandler.Result addPseudowiresBulk(List<DefaultL2TunnelDescription> bulkPseudowires) {
 
-        Set<L2TunnelDescription> pseudowires = l2TunnelHandler.getL2Descriptions();
-
+        // get both added and pending pseudowires
+        List<L2TunnelDescription> pseudowires = new ArrayList<>();
+        pseudowires.addAll(l2TunnelHandler.getL2Descriptions(false));
+        pseudowires.addAll(l2TunnelHandler.getL2Descriptions(true));
         pseudowires.addAll(bulkPseudowires);
+
         Set<L2TunnelDescription> newPseudowires = new HashSet(bulkPseudowires);
 
         // check global validity for all the new pseudowires, if it fails
         // do not add any of them
+        log.debug("Verifying set of pseudowires {}", pseudowires);
         boolean res = configurationValidity(pseudowires);
         if (res) {
+            log.debug("Pseudowire configuration is valid, deploying pseudowires!");
             l2TunnelHandler.deploy(newPseudowires);
+
             return L2TunnelHandler.Result.SUCCESS;
         } else {
             log.error("Bulk pseudowires {} can not be added, error in global configuration!",
@@ -595,22 +602,20 @@
     @Override
     public L2TunnelHandler.Result addPseudowire(L2TunnelDescription l2TunnelDescription) {
 
-        Set<L2TunnelDescription> newPseudowires = l2TunnelHandler.getL2Descriptions();
+        // get both added and pending pseudowires
+        List<L2TunnelDescription> newPseudowires = new ArrayList<>();
+        newPseudowires.addAll(l2TunnelHandler.getL2Descriptions(false));
+        newPseudowires.addAll(l2TunnelHandler.getL2Descriptions(true));
 
-        // corner case where we try to add the exact same pseudowire
-        if (newPseudowires.contains(l2TunnelDescription)) {
-            log.info("Pseudowire with {} already exists!", l2TunnelDescription);
-            return L2TunnelHandler.Result.SUCCESS;
-        }
-        // add the new pseudowire to the Set
+        // add the new pseudowire to the List
         newPseudowires.add(l2TunnelDescription);
-        // validate the new set of pseudowires
+        // validate the new list of pseudowires
         boolean res = configurationValidity(newPseudowires);
         if (res) {
             // deploy a set with ONLY the new pseudowire
-            newPseudowires = new HashSet<>();
-            newPseudowires.add(l2TunnelDescription);
-            l2TunnelHandler.deploy(newPseudowires);
+            Set<L2TunnelDescription> pwToDeploy = new HashSet<>();
+            pwToDeploy.add(l2TunnelDescription);
+            l2TunnelHandler.deploy(pwToDeploy);
 
             log.info("Pseudowire with {} deployment started, check log for any errors in this process!",
                      l2TunnelDescription.l2Tunnel().tunnelId());
@@ -624,34 +629,31 @@
     @Override
     public L2TunnelHandler.Result removePseudowire(Integer pwId) {
 
-        List<L2Tunnel> tunnels = getL2Tunnels();
-        List<L2TunnelPolicy> policies = getL2Policies();
+        // get both added and pending pseudowires
+        Set<L2TunnelDescription> pseudowires = l2TunnelHandler.getL2Descriptions(false)
+                .stream()
+                .filter(pw -> pw.l2Tunnel().tunnelId() == pwId)
+                .collect(Collectors.toSet());
+        Set<L2TunnelDescription> pendingPseudowires = l2TunnelHandler.getL2Descriptions(true)
+                .stream()
+                .filter(pw -> pw.l2Tunnel().tunnelId() == pwId)
+                .collect(Collectors.toSet());
 
-        // get the pseudowire, if it exists
-        List<L2TunnelDescription> pseudowires = tunnels.stream().map(l2Tunnel -> {
-            L2TunnelPolicy policy = null;
-            for (L2TunnelPolicy l2Policy : policies) {
-                if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
-                    policy = l2Policy;
-                    break;
-                }
-            }
-
-            return new DefaultL2TunnelDescription(l2Tunnel, policy);
-        }).filter(l2desc ->
-            l2desc.l2Tunnel().tunnelId() == pwId
-        ).collect(Collectors.toList());
-
-        if (pseudowires.size() == 0) {
+        if ((pendingPseudowires.size() == 0) && (pseudowires.size() == 0)) {
             log.error("Pseudowire with id {} does not exist", pwId);
             return L2TunnelHandler.Result.REMOVAL_ERROR;
-        } else {
-
+        }
+        if (pendingPseudowires.size() != 0) {
+            log.info("Remove pseudowire from pending store!");
+            // will fill when we implement failure mechanism detection.
+        }
+        if (pseudowires.size() != 0) {
             l2TunnelHandler.tearDown(new HashSet<>(pseudowires));
             log.info("Removal of pseudowire with {} started, check log for any errors in this process!",
                      pwId);
-            return L2TunnelHandler.Result.SUCCESS;
         }
+
+        return L2TunnelHandler.Result.SUCCESS;
     }
 
     @Override
diff --git a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index dc3d7b1..c329d3d 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
@@ -92,9 +92,10 @@
     /**
      * Returns the l2 tunnel descriptions.
      *
+     * @param pending if true fetch pending pseudowires, else fetch installed
      * @return set of l2 tunnel descriptions.
      */
-    Set<L2TunnelDescription> getL2TunnelDescriptions();
+    Set<L2TunnelDescription> getL2TunnelDescriptions(boolean pending);
 
     /**
      * Returns all l2 tunnels of pseudowires.
diff --git a/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java b/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java
index b69763d..4e2c4b7 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java
@@ -118,7 +118,6 @@
 
         L2TunnelDescription pw = new DefaultL2TunnelDescription(tun, policy);
         L2TunnelHandler.Result res = srService.addPseudowire(pw);
-
         switch (res) {
             case ADDITION_ERROR:
                 print("Pseudowire could not be added!");
diff --git a/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java b/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
index a633a8a..f38fe80 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
@@ -19,14 +19,7 @@
 import org.onlab.packet.VlanId;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.segmentrouting.SegmentRoutingService;
-import org.onosproject.segmentrouting.pwaas.DefaultL2TunnelDescription;
-import org.onosproject.segmentrouting.pwaas.L2Tunnel;
 import org.onosproject.segmentrouting.pwaas.L2TunnelDescription;
-import org.onosproject.segmentrouting.pwaas.L2TunnelPolicy;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
 /**
  * Command to show the pseudowires.
  */
@@ -39,7 +32,8 @@
                     "   mode : %s, sdTag : %s, pwLabel : %s \n" +
                     "   cP1 : %s , cP1OuterTag : %s, cP1InnerTag : %s \n" +
                     "   cP2 : %s , cP2OuterTag : %s, cP2InnerTag : %s \n" +
-                    "   transportVlan : %s";
+                    "   transportVlan : %s \n" +
+                    "   pending = %s";
 
     @Override
     protected void execute() {
@@ -47,30 +41,14 @@
         SegmentRoutingService srService =
                 AbstractShellCommand.get(SegmentRoutingService.class);
 
-        List<L2Tunnel> tunnels = srService.getL2Tunnels();
-        List<L2TunnelPolicy> policies = srService.getL2Policies();
+        srService.getL2TunnelDescriptions(false)
+                .forEach(pw -> printPseudowire(pw, false));
 
-        // combine polices and tunnels to pseudowires
-        List<L2TunnelDescription> pseudowires = tunnels.stream()
-                                    .map(l2Tunnel -> {
-                                            L2TunnelPolicy policy = null;
-                                            for (L2TunnelPolicy l2Policy : policies) {
-                                                if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
-                                                    policy = l2Policy;
-                                                    break;
-                                                }
-                                            }
-
-                                            return new DefaultL2TunnelDescription(l2Tunnel, policy);
-                                    })
-                                    .collect(Collectors.toList());
-
-        pseudowires.forEach(pw -> printPseudowire(pw));
+        srService.getL2TunnelDescriptions(true)
+                .forEach(pw -> printPseudowire(pw, true));
     }
 
-    private void printPseudowire(L2TunnelDescription pseudowire) {
-
-
+    private void printPseudowire(L2TunnelDescription pseudowire, boolean pending) {
         VlanId vlan = pseudowire.l2Tunnel().transportVlan().equals(VlanId.vlanId((short) 4094)) ?
                 VlanId.NONE : pseudowire.l2Tunnel().transportVlan();
 
@@ -79,6 +57,6 @@
               pseudowire.l2TunnelPolicy().cP1(), pseudowire.l2TunnelPolicy().cP1OuterTag(),
               pseudowire.l2TunnelPolicy().cP1InnerTag(), pseudowire.l2TunnelPolicy().cP2(),
               pseudowire.l2TunnelPolicy().cP2OuterTag(), pseudowire.l2TunnelPolicy().cP2InnerTag(),
-              vlan);
+              vlan, pending);
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java b/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java
index acaf9f6..5e2817b 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java
@@ -96,6 +96,16 @@
      */
     private final ConsistentMap<String, L2Tunnel> l2TunnelStore;
 
+    /**
+     * To store pending tunnels that need to be installed.
+     */
+    private final ConsistentMap<String, L2Tunnel> pendingL2TunnelStore;
+
+    /**
+     * To store pending policies that need to be installed.
+     */
+    private final ConsistentMap<String, L2TunnelPolicy> pendingL2PolicyStore;
+
     private final KryoNamespace.Builder l2TunnelKryo;
 
     /**
@@ -154,6 +164,19 @@
                 .withSerializer(Serializer.using(l2TunnelKryo.build()))
                 .build();
 
+        pendingL2PolicyStore = srManager.storageService
+                .<String, L2TunnelPolicy>consistentMapBuilder()
+                .withName("onos-l2-pending-policy-store")
+                .withSerializer(Serializer.using(l2TunnelKryo.build()))
+                .build();
+
+        pendingL2TunnelStore = srManager.storageService
+                .<String, L2Tunnel>consistentMapBuilder()
+                .withName("onos-l2-pending-tunnel-store")
+                .withSerializer(Serializer.using(l2TunnelKryo.build()))
+                .build();
+
+
         vlanStore = srManager.storageService.<VlanId>setBuilder()
                 .withName("onos-transport-vlan-store")
                 .withSerializer(Serializer.using(
@@ -176,12 +199,12 @@
     }
 
     @Override
-    public Set<L2TunnelDescription> getL2Descriptions() {
-        List<L2Tunnel> tunnels = getL2Tunnels();
-        List<L2TunnelPolicy> policies = getL2Policies();
+    public Set<L2TunnelDescription> getL2Descriptions(boolean pending) {
+        if (!pending) {
+            List<L2Tunnel> tunnels = getL2Tunnels();
+            List<L2TunnelPolicy> policies = getL2Policies();
 
-        // determine affected pseudowires and update them at once
-        return tunnels.stream()
+            return tunnels.stream()
                 .map(l2Tunnel -> {
                     L2TunnelPolicy policy = null;
                     for (L2TunnelPolicy l2Policy : policies) {
@@ -194,13 +217,26 @@
                     return new DefaultL2TunnelDescription(l2Tunnel, policy);
                 })
                 .collect(Collectors.toSet());
+        } else {
+            List<L2Tunnel> tunnels = getL2PendingTunnels();
+            List<L2TunnelPolicy> policies = getL2PendingPolicies();
+
+            return tunnels.stream()
+                    .map(l2Tunnel -> {
+                        L2TunnelPolicy policy = null;
+                        for (L2TunnelPolicy l2Policy : policies) {
+                            if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
+                                policy = l2Policy;
+                                break;
+                            }
+                        }
+
+                        return new DefaultL2TunnelDescription(l2Tunnel, policy);
+                    })
+                    .collect(Collectors.toSet());
+        }
     }
 
-    /**
-     * Returns all L2 Policies.
-     *
-     * @return List of policies
-     */
     @Override
     public List<L2TunnelPolicy> getL2Policies() {
 
@@ -211,11 +247,6 @@
                 .collect(Collectors.toList()));
     }
 
-    /**
-     * Returns all L2 Tunnels.
-     *
-     * @return List of tunnels.
-     */
     @Override
     public List<L2Tunnel> getL2Tunnels() {
 
@@ -227,33 +258,108 @@
     }
 
     @Override
-    public void processLinkDown(Link link) {
+    public List<L2TunnelPolicy> getL2PendingPolicies() {
 
-        List<L2Tunnel> tunnels = getL2Tunnels();
-        List<L2TunnelPolicy> policies = getL2Policies();
+        return new ArrayList<>(pendingL2PolicyStore
+                                       .values()
+                                       .stream()
+                                       .map(Versioned::value)
+                                       .collect(Collectors.toList()));
+    }
 
-        // determine affected pseudowires and update them at once
-        Set<L2TunnelDescription> pwToUpdate = tunnels
-                .stream()
-                .filter(tun -> tun.pathUsed().contains(link))
-                .map(l2Tunnel -> {
-                        L2TunnelPolicy policy = null;
-                        for (L2TunnelPolicy l2Policy : policies) {
-                            if (l2Policy.tunnelId() == l2Tunnel.tunnelId()) {
-                                policy = l2Policy;
-                                break;
-                            }
-                        }
+    @Override
+    public List<L2Tunnel> getL2PendingTunnels() {
 
-                        return new DefaultL2TunnelDescription(l2Tunnel, policy);
-                })
-                .collect(Collectors.toSet());
+        return new ArrayList<>(pendingL2TunnelStore
+                                       .values()
+                                       .stream()
+                                       .map(Versioned::value)
+                                       .collect(Collectors.toList()));
+    }
 
+    /**
+     * Manages intermediate filtering rules.
+     *
+     * For leaf-spine-spine pseudowires we need to install a special filtering
+     * rule in the intermediate spine for the appropriate transport vlan.
+     *
+     * @param pw The pseudowire, it will have the path and the transport vlan.
+     */
+    private Result manageIntermediateFiltering(L2TunnelDescription pw, boolean leafSpinePw) {
 
-        log.info("Pseudowires affected by link failure : {}, rerouting them...", pwToUpdate);
+        // only leaf-spine-spine should need intermediate rules for now
+        if (!leafSpinePw) {
+            return Result.SUCCESS;
+        }
+        if (pw.l2Tunnel().pathUsed().size() != 2) {
+            return Result.SUCCESS;
+        }
 
-        // update all pseudowires
-        pwToUpdate.forEach(tun -> updatePw(tun, tun));
+        List<Link> path = pw.l2Tunnel().pathUsed();
+        DeviceId intermediateSpineId = pw.l2Tunnel().pathUsed().get(0).dst().deviceId();
+        L2Tunnel l2Tunnel = pw.l2Tunnel();
+
+        log.info("Installing intermediate filtering rules for spine {} , for pseudowire {}",
+                 intermediateSpineId, pw.l2Tunnel().tunnelId());
+
+        MacAddress dstMac;
+        try {
+            dstMac = srManager.deviceConfiguration().getDeviceMac(intermediateSpineId);
+        } catch (Exception e) {
+            log.info("Device not found in configuration, no programming of MAC address");
+            dstMac = null;
+        }
+
+        PortNumber inPort;
+
+        inPort = path.get(0).dst().port();
+
+        log.debug("Populating filtering objective for pseudowire transport" +
+                         " with vlan = {}, port = {}, mac = {} for device {}",
+                 l2Tunnel.transportVlan(),
+                 inPort,
+                 dstMac,
+                 intermediateSpineId);
+
+        FilteringObjective.Builder filteringObjectiveBuilder =
+                createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
+        DefaultObjectiveContext context = new DefaultObjectiveContext((objective) ->
+                                                                              log.debug("Special filtObj for  " +
+                                                                                                "for {} populated",
+                                                                                        l2Tunnel.tunnelId()),
+                                                                      (objective, error) ->
+                                                                              log.warn("Failed to populate " +
+                                                                                               "special filtObj " +
+                                                                                               "rule for {}: {}",
+                                                                                       l2Tunnel.tunnelId(), error));
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        filteringObjectiveBuilder.withMeta(treatment.build());
+        srManager.flowObjectiveService.filter(intermediateSpineId, filteringObjectiveBuilder.add(context));
+
+        inPort = path.get(1).src().port();
+
+        log.debug("Populating filtering objective for pseudowire transport" +
+                         " with vlan = {}, port = {}, mac = {} for device {}",
+                 l2Tunnel.transportVlan(),
+                 inPort,
+                 dstMac,
+                 intermediateSpineId);
+
+        filteringObjectiveBuilder =
+                createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
+        context = new DefaultObjectiveContext((objective) ->
+                                                      log.debug("Special filtObj for  " + "for {} populated",
+                                                                l2Tunnel.tunnelId()),
+                                              (objective, error) ->
+                                                      log.warn("Failed to populate " +
+                                                                       "special filtObj " +
+                                                                       "rule for {}: {}",
+                                                               l2Tunnel.tunnelId(), error));
+        treatment = DefaultTrafficTreatment.builder();
+        filteringObjectiveBuilder.withMeta(treatment.build());
+        srManager.flowObjectiveService.filter(intermediateSpineId, filteringObjectiveBuilder.add(context));
+
+        return Result.SUCCESS;
     }
 
     /**
@@ -270,7 +376,6 @@
      */
     private 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;
@@ -329,23 +434,44 @@
     /**
      * Adds a single pseudowire.
      *
-     * @param pw The pseudowire
-     * @param spinePw True if pseudowire is from leaf to spine
+     * @param pw The pseudowire to deploy
+     * @param removeFromPending if to remove the pseudowire from the pending store
      * @return result of pseudowire deployment
      */
-    private Result deployPseudowire(L2TunnelDescription pw, boolean spinePw) {
+    private Result deployPseudowire(L2TunnelDescription pw, boolean removeFromPending) {
 
         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;
         }
 
+        // leafSpinePw determines if this is a leaf-leaf
+        // or leaf-spine pseudowire
+        boolean leafSpinePw;
+        ConnectPoint cp1 = pw.l2TunnelPolicy().cP1();
+        ConnectPoint cp2 = pw.l2TunnelPolicy().cP2();
+        try {
+            // differentiate between leaf-leaf pseudowires and leaf-spine
+            if (!srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
+                    !srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
+                log.error("Can not deploy pseudowire from spine to spine!");
+                return Result.INTERNAL_ERROR;
+            } else if (srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
+                    srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
+                leafSpinePw = false;
+            } else {
+                leafSpinePw = true;
+            }
+        } catch (DeviceConfigNotFoundException e) {
+            log.error("A device for pseudowire connection points does not exist.");
+            return Result.INTERNAL_ERROR;
+        }
+
         // get path here, need to use the same for fwd and rev direction
         List<Link> path = getPath(pw.l2TunnelPolicy().cP1(),
                                   pw.l2TunnelPolicy().cP2());
@@ -363,18 +489,18 @@
             return INTERNAL_ERROR;
         }
 
-        // spinePw signifies if we have a leaf-spine pw
-        // thus only one label should be pushed (that of pw)
-        // if size>1 we need to push intermediate labels also.
+        // oneHope flag is used to determine if we need to
+        // install transit mpls rules
+        boolean oneHop = true;
         if (path.size() > 1) {
-            spinePw = false;
+            oneHop = false;
         }
 
         fwdNextHop = path.get(0);
         revNextHop = reverseLink(path.get(path.size() - 1));
 
         pw.l2Tunnel().setPath(path);
-        pw.l2Tunnel().setTransportVlan(determineTransportVlan(spinePw));
+        pw.l2Tunnel().setTransportVlan(determineTransportVlan(leafSpinePw));
 
         // next hops for next objectives
         log.info("Deploying process : Establishing forward direction for pseudowire {}", l2TunnelId);
@@ -390,7 +516,8 @@
                                       pw.l2TunnelPolicy().cP2(),
                                       FWD,
                                       fwdNextHop,
-                                      spinePw,
+                                      leafSpinePw,
+                                      oneHop,
                                       egressVlan);
         if (result != SUCCESS) {
             log.info("Deploying process : Error in deploying pseudowire initiation for CP1");
@@ -414,7 +541,8 @@
                                        pw.l2TunnelPolicy().cP2(),
                                        egressVlan,
                                        FWD,
-                                      spinePw);
+                                       leafSpinePw,
+                                       oneHop);
 
         if (result != SUCCESS) {
             log.info("Deploying process : Error in deploying pseudowire termination for CP1");
@@ -435,7 +563,8 @@
                                        pw.l2TunnelPolicy().cP1(),
                                        REV,
                                        revNextHop,
-                                       spinePw,
+                                       leafSpinePw,
+                                       oneHop,
                                        egressVlan);
         if (result != SUCCESS) {
             log.info("Deploying process : Error in deploying pseudowire initiation for CP2");
@@ -455,21 +584,41 @@
         }
 
         result = deployPseudoWireTerm(pw.l2Tunnel(),
-                                       pw.l2TunnelPolicy().cP1(),
-                                       egressVlan,
-                                       REV,
-                                      spinePw);
+                                      pw.l2TunnelPolicy().cP1(),
+                                      egressVlan,
+                                      REV,
+                                      leafSpinePw,
+                                      oneHop);
 
         if (result != SUCCESS) {
             log.info("Deploying process : Error in deploying pseudowire termination for CP2");
             return Result.ADDITION_ERROR;
         }
 
+        result = manageIntermediateFiltering(pw, leafSpinePw);
+        if (result != SUCCESS) {
+            log.info("Deploying process : Error in installing intermediate rules for tagged transport!");
+            return Result.ADDITION_ERROR;
+        }
+
         log.info("Deploying process : Updating relevant information for pseudowire {}", l2TunnelId);
 
-        // Populate stores
+        // Populate stores as the final step of the process
         l2TunnelStore.put(Long.toString(l2TunnelId), pw.l2Tunnel());
         l2PolicyStore.put(Long.toString(l2TunnelId), pw.l2TunnelPolicy());
+        // if removeFromPending then remove the information from the pending stores.
+        if (removeFromPending) {
+            // check existence of tunnels/policy in the pending store, if one is missing abort!
+            Versioned<L2Tunnel> l2TunnelVersioned = pendingL2TunnelStore.get(Long.toString(l2TunnelId));
+            Versioned<L2TunnelPolicy> l2TunnelPolicyVersioned = pendingL2PolicyStore.get(Long.toString(l2TunnelId));
+            if ((l2TunnelVersioned == null) || (l2TunnelPolicyVersioned == null)) {
+                log.warn("Removal process : Policy and/or tunnel missing for tunnel id {} in pending store",
+                         l2TunnelId);
+            } else {
+                pendingL2TunnelStore.remove(Long.toString(l2TunnelId));
+                pendingL2PolicyStore.remove(Long.toString(l2TunnelId));
+            }
+        }
 
         return Result.SUCCESS;
     }
@@ -484,31 +633,9 @@
         Result result;
 
         for (L2TunnelDescription currentL2Tunnel : pwToAdd) {
-            ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
-            ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
+            // add pseudowires one by one
             long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
-
-
-            try {
-                // differentiate between leaf-leaf pseudowires and leaf-spine
-                // and pass the appropriate flag in them.
-                if (!srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
-                    !srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
-                    log.warn("Can not deploy pseudowire from spine to spine!");
-                    result = Result.INTERNAL_ERROR;
-                } else if (srManager.deviceConfiguration().isEdgeDevice(cp1.deviceId()) &&
-                     srManager.deviceConfiguration().isEdgeDevice(cp2.deviceId())) {
-                    log.info("Deploying a leaf-leaf pseudowire {}", tunnelId);
-                    result = deployPseudowire(currentL2Tunnel, false);
-                } else {
-                    log.info("Deploying a leaf-spine pseudowire {}", tunnelId);
-                    result = deployPseudowire(currentL2Tunnel, true);
-                }
-            } catch (DeviceConfigNotFoundException e) {
-                log.error("Exception caught when deploying pseudowire", e.toString());
-                result = Result.INTERNAL_ERROR;
-            }
-
+            result = deployPseudowire(currentL2Tunnel, false);
             switch (result) {
                 case INTERNAL_ERROR:
                     log.warn("Could not deploy pseudowire {}, internal error!", tunnelId);
@@ -541,6 +668,7 @@
      * @param oldPw the pseudo wire to remove
      * @param newPw the pseudo wire to add
      */
+    /*
     private void updatePw(L2TunnelDescription oldPw,
                          L2TunnelDescription newPw) {
         ConnectPoint oldCp1 = oldPw.l2TunnelPolicy().cP1();
@@ -653,9 +781,11 @@
         // spinePw signifies if we have a leaf-spine pw
         // thus only one label should be pushed (that of pw)
         // if size>1 we need to push intermediate labels also.
+        boolean oneHope = true;
         if (path.size() > 1) {
-            newPwSpine = false;
+            oneHope = false;
         }
+        final boolean oneHopeFinal = oneHope;
 
         fwdNextHop = path.get(0);
         revNextHop = reverseLink(path.get(path.size() - 1));
@@ -681,7 +811,7 @@
                 log.debug("Update process : Deploying new fwd pw for {}", tunnelId);
                 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP1(),
                                                            newPw.l2TunnelPolicy().cP2(), FWD,
-                                                           fwdNextHop, finalNewPwSpine, egressVlanId);
+                                                           fwdNextHop, finalNewPwSpine, oneHopeFinal, egressVlanId);
                 if (lamdaResult != SUCCESS) {
                     return;
                 }
@@ -694,7 +824,7 @@
                     return;
                 }
                 deployPseudoWireTerm(newPw.l2Tunnel(), newPw.l2TunnelPolicy().cP2(),
-                                      egressVlanId, FWD, finalNewPwSpine);
+                                      egressVlanId, FWD, finalNewPwSpine, oneHopeFinal);
 
             }
         });
@@ -712,7 +842,7 @@
                                                            newPw.l2TunnelPolicy().cP2(),
                                                            newPw.l2TunnelPolicy().cP1(),
                                                            REV,
-                                                           revNextHop, finalNewPwSpine, egressVlanId);
+                                                           revNextHop, finalNewPwSpine, oneHopeFinal, egressVlanId);
                 if (lamdaResult != SUCCESS) {
                     return;
                 }
@@ -729,22 +859,22 @@
                 deployPseudoWireTerm(newPw.l2Tunnel(),
                                       newPw.l2TunnelPolicy().cP1(),
                                       egressVlanId,
-                                      REV, finalNewPwSpine);
+                                      REV, finalNewPwSpine, oneHopeFinal);
             }
         });
     }
+    */
 
     /**
-     * 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.
+     * Tears down connection points of pseudowires. We can either tear down both connection points,
+     * or each one of them.
      *
-     * @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
+     * @param l2TunnelId The tunnel id for this pseudowire.
+     * @param tearDownFirst Boolean, true if we want to tear down cp1
+     * @param tearDownSecond Boolean, true if we want to tear down cp2
+     * @return
      */
-    private Result tearDownPseudowire(long l2TunnelId) {
+    private Result tearDownConnectionPoints(long l2TunnelId, boolean tearDownFirst, boolean tearDownSecond) {
 
         CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
@@ -766,7 +896,7 @@
         }
 
         L2TunnelDescription pwToRemove = new DefaultL2TunnelDescription(l2TunnelVersioned.value(),
-                                                                               l2TunnelPolicyVersioned.value());
+                                                                        l2TunnelPolicyVersioned.value());
 
         // remove the tunnels and the policies from the store
         l2PolicyStore.remove(Long.toString(l2TunnelId));
@@ -777,92 +907,103 @@
             vlanStore.remove(pwToRemove.l2Tunnel().transportVlan());
         }
 
-        log.info("Removal process : Tearing down forward direction of pseudowire {}", l2TunnelId);
+        if (tearDownFirst) {
+            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);
+            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);
-            }
-        });
+            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);
-            }
-        });
+            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);
+        if (tearDownSecond) {
+            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());
+            VlanId 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);
+            // 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);
-            }
-        });
+            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);
-            }
-        });
+            revTermNextFuture.thenAcceptAsync(status -> {
+                if (status == null) {
+                    tearDownPseudoWireTerm(pwToRemove.l2Tunnel(),
+                                           pwToRemove.l2TunnelPolicy().cP1(),
+                                           null,
+                                           REV);
+                }
+            });
+        }
 
         return Result.SUCCESS;
     }
 
+    /**
+     * 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 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 Result tearDownPseudowire(long l2TunnelId) {
+        return tearDownConnectionPoints(l2TunnelId, true, true);
+    }
+
     @Override
     public void tearDown(Set<L2TunnelDescription> pwToRemove) {
 
-        Result result;
-
-        // We remove all the pw in the configuration file.
         for (L2TunnelDescription currentL2Tunnel : pwToRemove) {
-            ConnectPoint cp1 = currentL2Tunnel.l2TunnelPolicy().cP1();
-            ConnectPoint cp2 = currentL2Tunnel.l2TunnelPolicy().cP2();
-            long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
 
-            // no need to differentiate here between leaf-leaf and leaf-spine, because
-            // the only change is in the groups, which we do not remove either way
+            long tunnelId = currentL2Tunnel.l2TunnelPolicy().tunnelId();
             log.info("Removing pseudowire {}", tunnelId);
 
-            result = tearDownPseudowire(tunnelId);
+            Result result = tearDownPseudowire(tunnelId);
             switch (result) {
                 case WRONG_PARAMETERS:
                     log.warn("Error in supplied parameters for the pseudowire removal with tunnel id {}!",
@@ -951,7 +1092,7 @@
      */
     private Result deployPseudoWireInit(L2Tunnel l2Tunnel, ConnectPoint ingress,
                                         ConnectPoint egress, Direction direction,
-                                        Link nextHop, boolean spinePw, VlanId termVlanId) {
+                                        Link nextHop, boolean spinePw, boolean oneHop, VlanId termVlanId) {
 
         if (nextHop == null) {
             log.warn("No path between ingress and egress cps for tunnel {}", l2Tunnel.tunnelId());
@@ -967,6 +1108,7 @@
                                                                          l2Tunnel,
                                                                          egress.deviceId(),
                                                                          spinePw,
+                                                                         oneHop,
                                                                          termVlanId);
 
         if (nextObjectiveBuilder == null) {
@@ -1016,12 +1158,13 @@
      * @return the result of the operation
      */
     private Result deployPseudoWireTerm(L2Tunnel l2Tunnel, ConnectPoint egress,
-                                        VlanId egressVlan, Direction direction, boolean spinePw) {
+                                        VlanId egressVlan, Direction direction, boolean spinePw, boolean oneHop) {
 
         // We create the group relative to the termination.
         NextObjective.Builder nextObjectiveBuilder = createNextObjective(TERMINATION, egress, null,
                                                                          l2Tunnel, egress.deviceId(),
                                                                          spinePw,
+                                                                         oneHop,
                                                                          egressVlan);
         if (nextObjectiveBuilder == null) {
             return INTERNAL_ERROR;
@@ -1238,12 +1381,15 @@
      * @param dstCp    the destination port
      * @param l2Tunnel the tunnel to support
      * @param egressId the egress device id
-     * @param spinePw if the pw involves a spine switch
+     * @param oneHop if the pw only has one hop, push only pw label
+     * @param leafSpinePw true if we want instantiate a leaf-spine or leaf-spine-spine pw
+     * @param termVlanId the outer vlan id of the packet exiting a termination point
      * @return the next objective to support the pipeline
      */
     private NextObjective.Builder createNextObjective(Pipeline pipeline, ConnectPoint srcCp,
                                                       ConnectPoint dstCp,  L2Tunnel l2Tunnel,
-                                                      DeviceId egressId, boolean spinePw, VlanId termVlanId) {
+                                                      DeviceId egressId, boolean leafSpinePw,
+                                                      boolean oneHop, VlanId termVlanId) {
         NextObjective.Builder nextObjBuilder;
         TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
         if (pipeline == INITIATION) {
@@ -1270,9 +1416,8 @@
                 treatmentBuilder.copyTtlOut();
             }
 
-            // if pw is leaf-to-leaf we need to
-            // add the routing label also
-            if (!spinePw) {
+            // if not oneHop install transit mpls labels also
+            if (!oneHop) {
                 // We retrieve the sr label from the config
                 // specific for pseudowire traffic
                 // using the egress leaf device id.
@@ -1309,13 +1454,14 @@
             }
             treatmentBuilder.setEthDst(neighborMac);
 
-            // if not a leaf-spine pw we need to POP the vlan at the output
-            // since we carry this traffic untagged.
-            if (!spinePw) {
+            // if true we need to pop the vlan because
+            // we instantiate a leaf to leaf pseudowire
+            if (!leafSpinePw) {
+                log.info("We should carry this traffic UNTAGGED!");
                 treatmentBuilder.popVlan();
             }
 
-            // set the appropriate transport vlan
+            // set the appropriate transport vlan from tunnel information
             treatmentBuilder.setVlanId(l2Tunnel.transportVlan());
         } else {
             // We create the next objective which
diff --git a/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java b/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
index ad4f50a..daabb07 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
@@ -16,8 +16,6 @@
 
 package org.onosproject.segmentrouting.pwaas;
 
-import org.onosproject.net.Link;
-
 import java.util.List;
 import java.util.Set;
 
@@ -27,9 +25,11 @@
     /**
      * Combines policies and tunnels to create descriptions.
      *
+     * @param pending if it is true return pending to be installed pseudowires
+     *                from the appropriate store, else return installed pseudowires
      * @return Set of l2 tunnel descriptions.
      */
-    Set<L2TunnelDescription> getL2Descriptions();
+    Set<L2TunnelDescription> getL2Descriptions(boolean pending);
 
     /**
      * Returns a copy of the l2 policies that exist in the store.
@@ -46,12 +46,18 @@
     List<L2Tunnel> getL2Tunnels();
 
     /**
-     * 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
+     * Returns a copy of the pending l2 policies that exist in the store.
+     *
+     * @return The l2 policies
      */
-    void processLinkDown(Link link);
+    List<L2TunnelPolicy> getL2PendingPolicies();
+
+    /**
+     * Returns a copy of the pending l2 tunnels that exist in the store.
+     *
+     * @return The l2 tunnels.
+     */
+    List<L2Tunnel> getL2PendingTunnels();
 
     /**
      * Helper function to handle the pw removal.
diff --git a/app/src/main/java/org/onosproject/segmentrouting/pwaas/PwaasUtil.java b/app/src/main/java/org/onosproject/segmentrouting/pwaas/PwaasUtil.java
index 9a7c749..d17ec10 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/pwaas/PwaasUtil.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/pwaas/PwaasUtil.java
@@ -16,8 +16,6 @@
 
 package org.onosproject.segmentrouting.pwaas;
 
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.MplsLabel;
 import org.onlab.packet.VlanId;
 import org.onosproject.cli.AbstractShellCommand;
@@ -25,6 +23,7 @@
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -42,10 +41,8 @@
 
     private static final Logger log = LoggerFactory.getLogger(PwaasUtil.class);
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private static DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private static InterfaceService intfService = AbstractShellCommand.get(InterfaceService.class);
 
     private PwaasUtil() {
@@ -440,7 +437,7 @@
 
     }
 
-    public static boolean configurationValidity(Set<L2TunnelDescription> pseudowires) {
+    public static boolean configurationValidity(List<L2TunnelDescription> pseudowires) {
 
         // structures to keep pw information
         // in order to see if instantiating them will create
@@ -458,15 +455,14 @@
         // Ideally we would like to return a String which could also return to
         // the user issuing the rest request for adding the pseudowire.
         try {
-
             // check that pseudowires can be instantiated in the network
             // we try to guarantee that all the pws will work before
             // instantiating any of them
             for (L2TunnelDescription pw : pseudowires) {
+                log.debug("Verifying pseudowire {}", pw);
                 verifyPseudoWire(pw, labelsUsed, vlanIds, tunIds);
             }
         } catch (Exception e) {
-
             log.error("Caught exception while validating pseudowire : {}", e.getMessage());
             return false;
         }
diff --git a/web/src/main/resources/definitions/PseudowireCreate.json b/web/src/main/resources/definitions/PseudowireCreate.json
index feddd18..315d01d 100644
--- a/web/src/main/resources/definitions/PseudowireCreate.json
+++ b/web/src/main/resources/definitions/PseudowireCreate.json
@@ -61,7 +61,7 @@
     },
     "pwLabel": {
       "type": "256",
-      "example": "",
+      "example": "1577",
       "description": "Pseudowire label."
     }
   }
diff --git a/web/src/main/resources/definitions/PseudowireCreateBulk.json b/web/src/main/resources/definitions/PseudowireCreateBulk.json
index 44db619..752e6eb 100644
--- a/web/src/main/resources/definitions/PseudowireCreateBulk.json
+++ b/web/src/main/resources/definitions/PseudowireCreateBulk.json
@@ -56,7 +56,7 @@
           },
           "pwLabel": {
             "type": "256",
-            "example": "",
+            "example": "1577",
             "description": "Pseudowire label."
           }
         }