Implemented the following PW features and fixes:

   - PW support for H-AGG topologies. Support for leaf-spine-spine
     leaf-spine-spine-leaf pseudowires.
   - Renamed pw commands with sr-pw-* pattern and also removed redundant output
    from them.
   - Enabled bulk addition / removal of pws from the rest api.
   - Modified diagnostics tool with the updated command name.

Change-Id: I708db9281d0082b160cbd713910b420ef7df9da3
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index 240d6e9..ff0f2a6 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -876,6 +876,39 @@
             }
         }
 
+        if (!targetIsEdge && !dest1IsEdge) {
+            // MPLS rules for inter-connected spines
+            // can be merged with above if, left it here for clarity
+            log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
+                              + "all MPLS rules", targetSw, destSw1);
+
+            result = rulePopulator.populateMplsRule(targetSw, destSw1,
+                                                        nextHops.get(destSw1),
+                                                        dest1RouterIpv4);
+            if (!result) {
+                return false;
+            }
+
+            if (dest1RouterIpv6 != null) {
+                int v4sid = 0, v6sid = 0;
+                try {
+                    v4sid = config.getIPv4SegmentId(destSw1);
+                    v6sid = config.getIPv6SegmentId(destSw1);
+                } catch (DeviceConfigNotFoundException e) {
+                    log.warn(e.getMessage());
+                }
+                if (v4sid != v6sid) {
+                    result = rulePopulator.populateMplsRule(targetSw, destSw1,
+                                                            nextHops.get(destSw1),
+                                                            dest1RouterIpv6);
+                    if (!result) {
+                        return false;
+                    }
+                }
+           }
+        }
+
+
         // To save on ECMP groups
         // avoid MPLS rules in non-edge-devices to non-edge-devices
         // avoid MPLS transit rules in edge-devices
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 65ffb53..1f035d7 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -556,6 +556,11 @@
     }
 
     @Override
+    public Set<L2TunnelDescription> getL2TunnelDescriptions() {
+        return l2TunnelHandler.getL2Descriptions();
+    }
+
+    @Override
     public List<L2Tunnel> getL2Tunnels() {
         return l2TunnelHandler.getL2Tunnels();
     }
@@ -566,43 +571,41 @@
     }
 
     @Override
+    public L2TunnelHandler.Result addPseudowiresBulk(List<DefaultL2TunnelDescription> bulkPseudowires) {
+
+        Set<L2TunnelDescription> pseudowires = l2TunnelHandler.getL2Descriptions();
+
+        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
+        boolean res = configurationValidity(pseudowires);
+        if (res) {
+            l2TunnelHandler.deploy(newPseudowires);
+            return L2TunnelHandler.Result.SUCCESS;
+        } else {
+            log.error("Bulk pseudowires {} can not be added, error in global configuration!",
+                     newPseudowires);
+            return L2TunnelHandler.Result.ADDITION_ERROR;
+        }
+    }
+
+    @Override
     public L2TunnelHandler.Result addPseudowire(L2TunnelDescription l2TunnelDescription) {
 
-        List<L2Tunnel> tunnels = getL2Tunnels();
-        List<L2TunnelPolicy> policies = getL2Policies();
-
-        // 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());
-
-
-        // creating a new list with the new pseudowire
-        Set<L2TunnelDescription> newPseudowires = new HashSet<>(pseudowires);
+        Set<L2TunnelDescription> newPseudowires = l2TunnelHandler.getL2Descriptions();
 
         // 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
         newPseudowires.add(l2TunnelDescription);
-
         // validate the new set of pseudowires
         boolean res = configurationValidity(newPseudowires);
         if (res) {
-
             // deploy a set with ONLY the new pseudowire
             newPseudowires = new HashSet<>();
             newPseudowires.add(l2TunnelDescription);
@@ -612,7 +615,6 @@
                      l2TunnelDescription.l2Tunnel().tunnelId());
             return L2TunnelHandler.Result.SUCCESS;
         } else {
-
             log.error("Pseudowire with {} can not be added!", l2TunnelDescription.l2Tunnel().tunnelId());
             return L2TunnelHandler.Result.ADDITION_ERROR;
         }
@@ -645,7 +647,6 @@
         } else {
 
             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;
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index 759495b..dc3d7b1 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
@@ -23,6 +23,7 @@
 import org.onosproject.net.PortNumber;
 import org.onosproject.segmentrouting.grouphandler.NextNeighbors;
 import org.onosproject.segmentrouting.mcast.McastRole;
+import org.onosproject.segmentrouting.pwaas.DefaultL2TunnelDescription;
 import org.onosproject.segmentrouting.pwaas.L2Tunnel;
 import org.onosproject.segmentrouting.pwaas.L2TunnelHandler;
 import org.onosproject.segmentrouting.pwaas.L2TunnelPolicy;
@@ -89,6 +90,13 @@
     List<Policy> getPolicies();
 
     /**
+     * Returns the l2 tunnel descriptions.
+     *
+     * @return set of l2 tunnel descriptions.
+     */
+    Set<L2TunnelDescription> getL2TunnelDescriptions();
+
+    /**
      * Returns all l2 tunnels of pseudowires.
      *
      * @return list of l2 tunnels
@@ -103,7 +111,7 @@
     List<L2TunnelPolicy> getL2Policies();
 
     /**
-     * Removes pseudowire. Used ONLY by the REST api.
+     * Removes pseudowire.
      *
      * @param pwId The id of the pseudowire.
      * @return SUCCESS if operation successful or a descriptive error otherwise.
@@ -111,7 +119,7 @@
     L2TunnelHandler.Result removePseudowire(Integer pwId);
 
     /**
-     * Adds a Pseudowire to the configuration.
+     * Adds a Pseudowire to the system.
      *
      * @param tunnel The pseudowire tunnel.
      * @return SUCCESS if operation is successful or a descriptive error otherwise.
@@ -119,6 +127,14 @@
     L2TunnelHandler.Result addPseudowire(L2TunnelDescription tunnel);
 
     /**
+     * Adss a set of pseudowires.
+     * @param l2TunnelDescriptions The pseudowires to add.
+     * @return SUCCESS if ALL pseudowires can be instantiated and are deployed, or a
+     *         a descriptive error otherwise, without deploying any pseudowire.
+     */
+    L2TunnelHandler.Result addPseudowiresBulk(List<DefaultL2TunnelDescription> l2TunnelDescriptions);
+
+    /**
      * Creates a policy.
      *
      * @param policy policy reference to create
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java
index ab80aff..b69763d 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireAddCommand.java
@@ -35,7 +35,7 @@
 /**
  * Command to add a pseuwodire.
  */
-@Command(scope = "onos", name = "pseudowire-add",
+@Command(scope = "onos", name = "sr-pw-add",
         description = "Add a pseudowire to the network configuration, if it already exists update it.")
 public class PseudowireAddCommand extends AbstractShellCommand {
 
@@ -121,10 +121,9 @@
 
         switch (res) {
             case ADDITION_ERROR:
-                print("Pseudowire could not be added, please check logs for more details!");
+                print("Pseudowire could not be added!");
                 break;
             case SUCCESS:
-                print("Pseudowire was added succesfully!");
                 break;
             default:
                 break;
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
index c48f633..a633a8a 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
@@ -30,7 +30,7 @@
 /**
  * Command to show the pseudowires.
  */
-@Command(scope = "onos", name = "pseudowires",
+@Command(scope = "onos", name = "sr-pw-list",
         description = "Lists all pseudowires")
 public class PseudowireListCommand extends AbstractShellCommand {
 
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireRemoveCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireRemoveCommand.java
index 0f027b2..305f0be 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireRemoveCommand.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/PseudowireRemoveCommand.java
@@ -29,7 +29,7 @@
 /**
  * Command to remove a pseudowire.
  */
-@Command(scope = "onos", name = "pseudowire-remove",
+@Command(scope = "onos", name = "sr-pw-remove",
         description = "Remove a pseudowire")
 public class PseudowireRemoveCommand extends AbstractShellCommand {
 
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java
index 4b3272d..acaf9f6 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelHandler.java
@@ -175,6 +175,27 @@
         // use it in the future.
     }
 
+    @Override
+    public Set<L2TunnelDescription> getL2Descriptions() {
+        List<L2Tunnel> tunnels = getL2Tunnels();
+        List<L2TunnelPolicy> policies = getL2Policies();
+
+        // determine affected pseudowires and update them at once
+        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.
      *
@@ -188,7 +209,6 @@
                 .stream()
                 .map(Versioned::value)
                 .collect(Collectors.toList()));
-
     }
 
     /**
@@ -204,7 +224,6 @@
                 .stream()
                 .map(Versioned::value)
                 .collect(Collectors.toList()));
-
     }
 
     @Override
@@ -298,6 +317,16 @@
     }
 
     /**
+     * Returns true if path size is valid according to the current logic.
+     *
+     * @param pathSize The size of the path
+     * @return True if path size is valid, false otherwise.
+     */
+    private boolean isValidPathSize(int pathSize) {
+        return ((pathSize >= 1) && (pathSize <= 4));
+    }
+
+    /**
      * Adds a single pseudowire.
      *
      * @param pw The pseudowire
@@ -327,31 +356,27 @@
 
         Link fwdNextHop;
         Link revNextHop;
-        if (!spinePw) {
-            if (path.size() != 2) {
-                log.info("Deploying process : Path between two leafs should have size of 2, for pseudowire {}",
-                         l2TunnelId);
-                return INTERNAL_ERROR;
-            }
-
-            fwdNextHop = path.get(0);
-            revNextHop = reverseLink(path.get(1));
-        } else {
-            if (path.size() != 1) {
-                log.info("Deploying process : Path between leaf spine should equal to 1, for pseudowire {}",
-                         l2TunnelId);
-                return INTERNAL_ERROR;
-            }
-
-            fwdNextHop = path.get(0);
-            revNextHop = reverseLink(path.get(0));
+        if (!isValidPathSize(path.size())) {
+            log.error("Deploying process : Path size for pseudowire should be of" +
+                              " one of the following sizes = [1, 2, 3, 4], for pseudowire {}",
+                      l2TunnelId);
+            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.
+        if (path.size() > 1) {
+            spinePw = false;
+        }
+
+        fwdNextHop = path.get(0);
+        revNextHop = reverseLink(path.get(path.size() - 1));
+
         pw.l2Tunnel().setPath(path);
         pw.l2Tunnel().setTransportVlan(determineTransportVlan(spinePw));
 
         // next hops for next objectives
-
         log.info("Deploying process : Establishing forward direction for pseudowire {}", l2TunnelId);
 
         VlanId egressVlan = determineEgressVlan(pw.l2TunnelPolicy().cP1OuterTag(),
@@ -618,24 +643,23 @@
         }
 
         Link fwdNextHop, revNextHop;
-        if (!finalNewPwSpine) {
-            if (path.size() != 2) {
-                log.error("Update process : Error, path between two leafs should have size of 2, for pseudowire {}",
-                         newPw.l2Tunnel().tunnelId());
-                return;
-            }
-            fwdNextHop = path.get(0);
-            revNextHop = reverseLink(path.get(1));
-        } else {
-            if (path.size() != 1) {
-                log.error("Update process : Error, path between leaf spine should equal to 1, for pseudowire {}",
-                         newPw.l2Tunnel().tunnelId());
-                return;
-            }
-            fwdNextHop = path.get(0);
-            revNextHop = reverseLink(path.get(0));
+        if (!isValidPathSize(path.size())) {
+            log.error("Deploying process : Path size for pseudowire should be of one of the following sizes" +
+                              " = [1, 2, 3, 4], for pseudowire {}",
+                      newPw.l2Tunnel().tunnelId());
+            return;
         }
 
+        // 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.
+        if (path.size() > 1) {
+            newPwSpine = false;
+        }
+
+        fwdNextHop = path.get(0);
+        revNextHop = reverseLink(path.get(path.size() - 1));
+
         // set new path and transport vlan.
         newPw.l2Tunnel().setPath(path);
         newPw.l2Tunnel().setTransportVlan(determineTransportVlan(newPwSpine));
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
index 5fce137..ad4f50a 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
@@ -25,6 +25,13 @@
     void init();
 
     /**
+     * Combines policies and tunnels to create descriptions.
+     *
+     * @return Set of l2 tunnel descriptions.
+     */
+    Set<L2TunnelDescription> getL2Descriptions();
+
+    /**
      * Returns a copy of the l2 policies that exist in the store.
      *
      * @return The l2 policies