ONOS-1438: Improved the routing rule population process for link add and failure; computes the routes changed from the link changes and populates the rules only for the routes.

Change-Id: Id4dbd80da37b333f2c19bc97333472dc8031481b
diff --git a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index 58d3262..50466c6 100644
--- a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -15,10 +15,13 @@
  */
 package org.onosproject.segmentrouting;
 
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.IpPrefix;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.flow.FlowRule;
 import org.slf4j.Logger;
@@ -39,6 +42,7 @@
     private SegmentRoutingManager srManager;
     private RoutingRulePopulator rulePopulator;
     private NetworkConfigHandler config;
+    private HashMap<DeviceId, ECMPShortestPathGraph> currentEcmpSpgMap;
     private Status populationStatus;
 
     /**
@@ -68,6 +72,7 @@
         this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
         this.config = checkNotNull(srManager.networkConfigHandler);
         this.populationStatus = Status.IDLE;
+        this.currentEcmpSpgMap = Maps.newHashMap();
     }
 
     /**
@@ -79,6 +84,7 @@
     public boolean populateAllRoutingRules() {
 
         populationStatus = Status.STARTED;
+        rulePopulator.resetCounter();
         log.info("Starts to populate routing rules");
 
         for (Device sw : srManager.deviceService.getDevices()) {
@@ -87,22 +93,233 @@
                 continue;
             }
 
-            ECMPShortestPathGraph ecmpSPG = new ECMPShortestPathGraph(sw.id(), srManager);
-            if (!populateEcmpRoutingRules(sw, ecmpSPG)) {
+            ECMPShortestPathGraph ecmpSpg = new ECMPShortestPathGraph(sw.id(), srManager);
+            if (!populateEcmpRoutingRules(sw.id(), ecmpSpg)) {
                 populationStatus = Status.ABORTED;
                 log.debug("Abort routing rule population");
                 return false;
             }
+            currentEcmpSpgMap.put(sw.id(), ecmpSpg);
 
             // TODO: Set adjacency routing rule for all switches
         }
 
         populationStatus = Status.SUCCEEDED;
-        log.info("Completes routing rule population");
+        log.info("Completes routing rule population. Total # of rules pushed : {}",
+                rulePopulator.getCounter());
         return true;
     }
 
-    private boolean populateEcmpRoutingRules(Device sw,
+    /**
+     * Populates the routing rules according to the route changes due to the link
+     * failure or link add. It computes the routes changed due to the link changes and
+     * repopulates the rules only for the routes.
+     *
+     * @param linkFail link failed, null for link added
+     * @return true if it succeeds to populate all rules, false otherwise
+     */
+    public boolean populateRoutingRulesForLinkStatusChange(Link linkFail) {
+
+        synchronized (populationStatus) {
+
+            if (populationStatus == Status.STARTED) {
+                return true;
+            }
+
+            Set<ArrayList<DeviceId>> routeChanges;
+            populationStatus = Status.STARTED;
+            if (linkFail == null) {
+                // Compare all routes of existing ECMP SPG with the new ones
+                routeChanges = computeRouteChange();
+            } else {
+                // Compare existing ECMP SPG only with the link removed
+                routeChanges = computeDamagedRoutes(linkFail);
+            }
+
+            if (routeChanges.isEmpty()) {
+                log.debug("No route changes for the link status change");
+                populationStatus = Status.SUCCEEDED;
+                return true;
+            }
+
+            if (repopulateRoutingRulesForRoutes(routeChanges)) {
+                populationStatus = Status.SUCCEEDED;
+                log.info("Complete to repopulate the rules. # of rules populated : {}",
+                        rulePopulator.getCounter());
+                return true;
+            } else {
+                populationStatus = Status.ABORTED;
+                log.warn("Failed to repopulate the rules.");
+                return false;
+            }
+        }
+    }
+
+    private boolean repopulateRoutingRulesForRoutes(Set<ArrayList<DeviceId>> routes) {
+        rulePopulator.resetCounter();
+        for (ArrayList<DeviceId> link: routes) {
+            if (link.size() == 1) {
+                ECMPShortestPathGraph ecmpSpg = new ECMPShortestPathGraph(link.get(0), srManager);
+                if (populateEcmpRoutingRules(link.get(0), ecmpSpg)) {
+                    currentEcmpSpgMap.put(link.get(0), ecmpSpg);
+                }
+                continue;
+            }
+            DeviceId src = link.get(0);
+            DeviceId dst = link.get(1);
+            ECMPShortestPathGraph ecmpSpg = new ECMPShortestPathGraph(dst, srManager);
+
+            currentEcmpSpgMap.put(dst, ecmpSpg);
+            HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
+                    ecmpSpg.getAllLearnedSwitchesAndVia();
+            for (Integer itrIdx : switchVia.keySet()) {
+                HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
+                        switchVia.get(itrIdx);
+                for (DeviceId targetSw : swViaMap.keySet()) {
+                    if (!targetSw.equals(src)) {
+                        continue;
+                    }
+                    Set<DeviceId> nextHops = new HashSet<>();
+                    for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
+                        if (via.isEmpty()) {
+                            nextHops.add(dst);
+                        } else {
+                            nextHops.add(via.get(0));
+                        }
+                    }
+                    if (!populateEcmpRoutingRulePartial(targetSw, dst, nextHops)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
+
+        Set<ArrayList<DeviceId>> routes = new HashSet<>();
+
+        for (Device sw : srManager.deviceService.getDevices()) {
+            if (srManager.mastershipService.
+                    getLocalRole(sw.id()) != MastershipRole.MASTER) {
+                continue;
+            }
+            ECMPShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id());
+            if (ecmpSpg == null) {
+                log.error("No existing ECMP path for switch {}", sw.id());
+                continue;
+            }
+            HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
+                    ecmpSpg.getAllLearnedSwitchesAndVia();
+            for (Integer itrIdx : switchVia.keySet()) {
+                HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
+                        switchVia.get(itrIdx);
+                for (DeviceId targetSw : swViaMap.keySet()) {
+                    DeviceId destSw = sw.id();
+                    Set<ArrayList<DeviceId>> subLinks =
+                            computeLinks(targetSw, destSw, swViaMap);
+                    for (ArrayList<DeviceId> alink: subLinks) {
+                        if (alink.get(0).equals(linkFail.src().deviceId()) &&
+                                alink.get(1).equals(linkFail.dst().deviceId())) {
+                            ArrayList<DeviceId> aRoute = new ArrayList<>();
+                            aRoute.add(targetSw);
+                            aRoute.add(destSw);
+                            routes.add(aRoute);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        return routes;
+    }
+
+    private Set<ArrayList<DeviceId>> computeRouteChange() {
+
+        Set<ArrayList<DeviceId>> routes = new HashSet<>();
+
+        for (Device sw : srManager.deviceService.getDevices()) {
+            if (srManager.mastershipService.
+                    getLocalRole(sw.id()) != MastershipRole.MASTER) {
+                continue;
+            }
+            ECMPShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id());
+            if (ecmpSpg == null) {
+                log.debug("No existing ECMP path for Switch {}", sw.id());
+                ArrayList<DeviceId> route = new ArrayList<>();
+                route.add(sw.id());
+                routes.add(route);
+                continue;
+            }
+            ECMPShortestPathGraph newEcmpSpg =
+                    new ECMPShortestPathGraph(sw.id(), srManager);
+            HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
+                    ecmpSpg.getAllLearnedSwitchesAndVia();
+            HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchViaUpdated =
+                    newEcmpSpg.getAllLearnedSwitchesAndVia();
+
+            for (Integer itrIdx : switchVia.keySet()) {
+                HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
+                        switchVia.get(itrIdx);
+                for (DeviceId srcSw : swViaMap.keySet()) {
+                    ArrayList<ArrayList<DeviceId>> via1 = swViaMap.get(srcSw);
+                    ArrayList<ArrayList<DeviceId>> via2 = getVia(switchViaUpdated, srcSw);
+                    if (!via1.equals(via2)) {
+                        ArrayList<DeviceId> route = new ArrayList<>();
+                        route.add(srcSw);
+                        route.add(sw.id());
+                        routes.add(route);
+                    }
+                }
+            }
+
+        }
+
+        return routes;
+    }
+
+    private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
+            ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId srcSw) {
+        for (Integer itrIdx : switchVia.keySet()) {
+            HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
+                    switchVia.get(itrIdx);
+            if (swViaMap.get(srcSw) == null) {
+                continue;
+            } else {
+                return swViaMap.get(srcSw);
+            }
+        }
+
+        return new ArrayList<>();
+    }
+
+    private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
+                                                  DeviceId dst,
+                       HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
+        Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
+        for (ArrayList<DeviceId> via : viaMap.get(src)) {
+            DeviceId linkSrc = src;
+            DeviceId linkDst = dst;
+            for (DeviceId viaDevice: via) {
+                ArrayList<DeviceId> link = new ArrayList<>();
+                linkDst = viaDevice;
+                link.add(linkSrc);
+                link.add(linkDst);
+                subLinks.add(link);
+                linkSrc = viaDevice;
+            }
+            ArrayList<DeviceId> link = new ArrayList<>();
+            link.add(linkSrc);
+            link.add(dst);
+            subLinks.add(link);
+        }
+
+        return subLinks;
+    }
+
+    private boolean populateEcmpRoutingRules(DeviceId destSw,
                                              ECMPShortestPathGraph ecmpSPG) {
 
         HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
@@ -111,7 +328,6 @@
             HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
                     switchVia.get(itrIdx);
             for (DeviceId targetSw : swViaMap.keySet()) {
-                DeviceId destSw = sw.id();
                 Set<DeviceId> nextHops = new HashSet<>();
 
                 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
diff --git a/src/main/java/org/onosproject/segmentrouting/IpHandler.java b/src/main/java/org/onosproject/segmentrouting/IpHandler.java
index cca8692..ee93f47 100644
--- a/src/main/java/org/onosproject/segmentrouting/IpHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/IpHandler.java
@@ -139,6 +139,7 @@
                     OutboundPacket packet = new DefaultOutboundPacket(deviceId,
                             treatment, ByteBuffer.wrap(eth.serialize()));
                     srManager.packetService.emit(packet);
+                    ipPacketQueue.get(destIpAddress).remove(ipPacket);
                 }
             }
         }
diff --git a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index e5cf69f..0a466de 100644
--- a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -40,6 +40,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -47,8 +48,9 @@
 
     private static final Logger log = LoggerFactory.getLogger(RoutingRulePopulator.class);
 
-    private SegmentRoutingManager srManager;
-    private NetworkConfigHandler config;
+    private final SegmentRoutingManager srManager;
+    private final NetworkConfigHandler config;
+    private AtomicLong rulePopulationCounter;
 
     /**
      * Creates a RoutingRulePopulator object.
@@ -58,6 +60,21 @@
     public RoutingRulePopulator(SegmentRoutingManager srManager) {
         this.srManager = srManager;
         this.config = checkNotNull(srManager.networkConfigHandler);
+        this.rulePopulationCounter = new AtomicLong(0);
+    }
+
+    /**
+     * Resets the population counter.
+     */
+    public void resetCounter() {
+        rulePopulationCounter.set(0);
+    }
+
+    /**
+     * Returns the number of rules populated.
+     */
+    public long getCounter() {
+        return rulePopulationCounter.get();
     }
 
     /**
@@ -87,6 +104,7 @@
                 srManager.appId, 600, false, FlowRule.Type.IP);
 
         srManager.flowRuleService.applyFlowRules(f);
+        rulePopulationCounter.incrementAndGet();
         log.debug("Flow rule {} is set to switch {}", f, deviceId);
     }
 
@@ -162,6 +180,7 @@
                 srManager.appId, 600, false, FlowRule.Type.IP);
 
         srManager.flowRuleService.applyFlowRules(f);
+        rulePopulationCounter.incrementAndGet();
         log.debug("IP flow rule {} is set to switch {}", f, deviceId);
 
         return true;
@@ -216,6 +235,7 @@
             FlowRule f = new DefaultFlowRule(deviceId, selector, treatment, 100,
                     srManager.appId, 600, false, FlowRule.Type.MPLS);
             srManager.flowRuleService.applyFlowRules(f);
+            rulePopulationCounter.incrementAndGet();
             log.debug("MPLS rule {} is set to {}", f, deviceId);
         }
 
diff --git a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index b5e1181..08d6220 100644
--- a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -151,8 +151,7 @@
                 groupHandler.createGroups();
                 groupHandlerMap.put(device.id(), groupHandler);
                 log.debug("Initiating default group handling for {}", device.id());
-
-                defaultRoutingHandler.startPopulationProcess();
+                defaultRoutingHandler.populateTtpRules(device.id());
             } else {
                 log.debug("Activate: Local role {} "
                                 + "is not MASTER for device {}",
@@ -162,6 +161,8 @@
             }
         }
 
+        defaultRoutingHandler.startPopulationProcess();
+
         log.info("Started");
     }
 
@@ -239,6 +240,8 @@
             switch (event.type()) {
             case DEVICE_ADDED:
             case PORT_REMOVED:
+            case DEVICE_UPDATED:
+            case DEVICE_AVAILABILITY_CHANGED:
                 scheduleEventHandlerIfNotScheduled(event);
                 break;
             default:
@@ -294,8 +297,12 @@
                     processLinkRemoved((Link) event.subject());
                 } else if (event.type() == GroupEvent.Type.GROUP_ADDED) {
                     processGroupAdded((Group) event.subject());
-                } else if (event.type() == DeviceEvent.Type.DEVICE_ADDED) {
-                    processDeviceAdded((Device) event.subject());
+                } else if (event.type() == DeviceEvent.Type.DEVICE_ADDED ||
+                        event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
+                        event.type() == DeviceEvent.Type.DEVICE_UPDATED) {
+                    if (deviceService.isAvailable(((Device) event.subject()).id())) {
+                        processDeviceAdded((Device) event.subject());
+                    }
                 } else if (event.type() == DeviceEvent.Type.PORT_REMOVED) {
                     processPortRemoved((Device) event.subject(),
                             ((DeviceEvent) event).port());
@@ -321,12 +328,12 @@
                 groupHandler.linkUp(link);
             }
         }
-        defaultRoutingHandler.startPopulationProcess();
+        defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(null);
     }
 
     private void processLinkRemoved(Link link) {
         log.debug("A link {} was removed", link.toString());
-        defaultRoutingHandler.startPopulationProcess();
+        defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(link);
     }