CORD-641 Bug fix for handling reconnected leaf switch
Also changed some ECMP graph node variables to consistenly use
'root' and 'target' instead of the generic 'dst' and 'src' terms.
Change-Id: I1965e6d4a13f3cd42ec5cce08b0b0c321625e554
diff --git a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index d7e4c3c..01ffd6e 100644
--- a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -16,6 +16,7 @@
package org.onosproject.segmentrouting;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onlab.packet.Ip4Address;
@@ -141,12 +142,15 @@
/**
* 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.
+ * repopulates the rules only for these routes. Note that when a switch goes
+ * away, all of its links fail as well, but this is handled as a single
+ * switch removal event.
*
- * @param linkFail link failed, null for link added
+ * @param failedLink the single failed link, or null for other conditions
+ * such as an added link or a removed switch
* @return true if it succeeds to populate all rules, false otherwise
*/
- public boolean populateRoutingRulesForLinkStatusChange(Link linkFail) {
+ public boolean populateRoutingRulesForLinkStatusChange(Link failedLink) {
statusLock.lock();
try {
@@ -173,16 +177,16 @@
log.trace("populateRoutingRulesForLinkStatusChange: "
+ "populationStatus is STARTED");
populationStatus = Status.STARTED;
- // optimized re-routing
- if (linkFail == null) {
- // Compare all routes of existing ECMP SPG with the new ones
+ // try optimized re-routing
+ if (failedLink == null) {
+ // Compare all routes of existing ECMP SPG to new ECMP SPG
routeChanges = computeRouteChange();
} else {
// Compare existing ECMP SPG only with the link removed
- routeChanges = computeDamagedRoutes(linkFail);
+ routeChanges = computeDamagedRoutes(failedLink);
}
- // null routeChanges indicates that full re-routing is required
+ // do full re-routing if optimized routing returns null routeChanges
if (routeChanges == null) {
return populateAllRoutingRules();
}
@@ -277,8 +281,11 @@
}
//Only if all the flows for all impacted routes to a
//specific target are pushed successfully, update the
- //ECMP graph for that target. (Or else the next event
- //would not see any changes in the ECMP graphs)
+ //ECMP graph for that target. Or else the next event
+ //would not see any changes in the ECMP graphs.
+ //In another case, the target switch has gone away, so
+ //routes can't be installed. In that case, the current map
+ //is updated here, without any flows being pushed.
currentEcmpSpgMap.put(impactedDevice,
updatedEcmpSpgMap.get(impactedDevice));
}
@@ -286,7 +293,7 @@
}
/**
- * Computes set of affected ECMP routes due to failed link. Assumes
+ * Computes set of affected routes due to failed link. Assumes
* previous ecmp shortest-path graph exists for a switch in order to compute
* affected routes. If such a graph does not exist, the method returns null.
*
@@ -319,26 +326,26 @@
HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
switchVia.get(itrIdx);
for (DeviceId targetSw : swViaMap.keySet()) {
- DeviceId destSw = sw.id();
+ DeviceId rootSw = sw.id();
if (log.isTraceEnabled()) {
- log.trace("TargetSwitch {} --> RootSwitch {}", targetSw, destSw);
+ log.trace("TargetSwitch {} --> RootSwitch {}", targetSw, rootSw);
for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
log.trace(" Via:");
via.forEach(e -> { log.trace(" {}", e); });
}
}
Set<ArrayList<DeviceId>> subLinks =
- computeLinks(targetSw, destSw, swViaMap);
+ computeLinks(targetSw, rootSw, swViaMap);
for (ArrayList<DeviceId> alink: subLinks) {
if ((alink.get(0).equals(linkFail.src().deviceId()) &&
alink.get(1).equals(linkFail.dst().deviceId()))
||
(alink.get(0).equals(linkFail.dst().deviceId()) &&
alink.get(1).equals(linkFail.src().deviceId()))) {
- log.debug("Impacted route:{}->{}", targetSw, destSw);
+ log.debug("Impacted route:{}->{}", targetSw, rootSw);
ArrayList<DeviceId> aRoute = new ArrayList<>();
aRoute.add(targetSw);
- aRoute.add(destSw);
+ aRoute.add(rootSw);
routes.add(aRoute);
break;
}
@@ -351,77 +358,109 @@
return routes;
}
+ /**
+ * Computes set of affected routes due to new links or failed switches.
+ *
+ * @return the set of affected routes which may be empty if no routes were
+ * affected
+ */
private Set<ArrayList<DeviceId>> computeRouteChange() {
- Set<ArrayList<DeviceId>> routes = new HashSet<>();
+ ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
+ ImmutableSet.builder();
for (Device sw : srManager.deviceService.getDevices()) {
- log.debug("Computing the impacted routes for device {}", sw.id());
- if (!srManager.mastershipService.isLocalMaster(sw.id())) {
+ DeviceId rootSw = sw.id();
+ log.debug("Computing the impacted routes for device {}", rootSw);
+ if (!srManager.mastershipService.isLocalMaster(rootSw)) {
log.debug("No mastership for {} ... skipping route optimization",
- sw.id());
+ rootSw);
continue;
}
if (log.isTraceEnabled()) {
- log.trace("link of {} - ", sw.id());
- for (Link link: srManager.linkService.getDeviceLinks(sw.id())) {
+ log.trace("link of {} - ", rootSw);
+ for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
log.trace("{} -> {} ", link.src().deviceId(), link.dst().deviceId());
}
}
- EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id());
- if (ecmpSpg == null) {
- log.debug("No existing ECMP graph for device {}", sw.id());
- ArrayList<DeviceId> route = new ArrayList<>();
- route.add(sw.id());
- routes.add(route);
+ EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
+ if (currEcmpSpg == null) {
+ log.debug("No existing ECMP graph for device {}", rootSw);
+ changedRoutesBuilder.add(Lists.newArrayList(rootSw));
continue;
}
- EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(sw.id());
- //currentEcmpSpgMap.put(sw.id(), newEcmpSpg);
- HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
- ecmpSpg.getAllLearnedSwitchesAndVia();
- HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchViaUpdated =
- newEcmpSpg.getAllLearnedSwitchesAndVia();
+ EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
+ if (log.isTraceEnabled()) {
+ log.trace("Root switch: {}", rootSw);
+ log.trace(" Current/Existing SPG: {}", currEcmpSpg);
+ log.trace(" New/Updated SPG: {}", newEcmpSpg);
+ }
+ // first use the updated/new map to compare to current/existing map
+ // as new links may have come up
+ changedRoutesBuilder.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
+ // then use the current/existing map to compare to updated/new map
+ // as switch may have been removed
+ changedRoutesBuilder.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
+ }
- for (Integer itrIdx : switchViaUpdated.keySet()) {
- HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMapUpdated =
- switchViaUpdated.get(itrIdx);
- for (DeviceId srcSw : swViaMapUpdated.keySet()) {
- ArrayList<ArrayList<DeviceId>> viaUpdated = swViaMapUpdated.get(srcSw);
- ArrayList<ArrayList<DeviceId>> via = getVia(switchVia, srcSw);
- if ((via == null) || !viaUpdated.equals(via)) {
- log.debug("Impacted route:{} -> {}", srcSw, sw.id());
- ArrayList<DeviceId> route = new ArrayList<>();
- route.add(srcSw);
- route.add(sw.id());
- routes.add(route);
- }
+ Set<ArrayList<DeviceId>> changedRoutes = changedRoutesBuilder.build();
+ for (ArrayList<DeviceId> route: changedRoutes) {
+ log.debug("Route changes Target -> Root");
+ if (route.size() == 1) {
+ log.debug(" : all -> {}", route.get(0));
+ } else {
+ log.debug(" : {} -> {}", route.get(0), route.get(1));
+ }
+ }
+ return changedRoutes;
+ }
+
+ /**
+ * For the root switch, searches all the target nodes reachable in the base
+ * graph, and compares paths to the ones in the comp graph.
+ *
+ * @param base the graph that is indexed for all reachable target nodes
+ * from the root node
+ * @param comp the graph that the base graph is compared to
+ * @param rootSw both ecmp graphs are calculated for the root node
+ * @return all the routes that have changed in the base graph
+ */
+ private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
+ EcmpShortestPathGraph comp,
+ DeviceId rootSw) {
+ ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
+ ImmutableSet.builder();
+ HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
+ base.getAllLearnedSwitchesAndVia();
+ HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
+ comp.getAllLearnedSwitchesAndVia();
+ for (Integer itrIdx : baseMap.keySet()) {
+ HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
+ baseMap.get(itrIdx);
+ for (DeviceId targetSw : baseViaMap.keySet()) {
+ ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
+ ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
+ if ((compPath == null) || !basePath.equals(compPath)) {
+ log.debug("Impacted route:{} -> {}", targetSw, rootSw);
+ ArrayList<DeviceId> route = new ArrayList<>();
+ route.add(targetSw);
+ route.add(rootSw);
+ changedRoutesBuilder.add(route);
}
}
}
-
- if (log.isTraceEnabled()) {
- for (ArrayList<DeviceId> link: routes) {
- log.trace("Route changes - ");
- if (link.size() == 1) {
- log.trace(" : all -> {}", link.get(0));
- } else {
- log.trace(" : {} -> {}", link.get(0), link.get(1));
- }
- }
- }
- return routes;
+ return changedRoutesBuilder.build();
}
private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
- ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId srcSw) {
+ ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
for (Integer itrIdx : switchVia.keySet()) {
HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
switchVia.get(itrIdx);
- if (swViaMap.get(srcSw) == null) {
+ if (swViaMap.get(targetSw) == null) {
continue;
} else {
- return swViaMap.get(srcSw);
+ return swViaMap.get(targetSw);
}
}
diff --git a/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java b/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java
index 36410fc..5e7e2ae 100644
--- a/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java
+++ b/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java
@@ -303,7 +303,10 @@
/**
* Return the complete info of the computed ECMP paths for each Device
- * learned in multiple iterations from the root Device.
+ * learned in multiple iterations from the root Device. The computed info
+ * returned is per iteration (Integer key of outer HashMap). In each
+ * iteration, for each device as root (DeviceId key of inner HashMap),
+ * the ECMP paths are detailed (2D array).
*
* @return the hash table of Devices learned in multiple Dijkstra
* iterations and corresponding ECMP paths in terms of Devices to
@@ -358,10 +361,12 @@
StringBuilder sBuilder = new StringBuilder();
for (Device device: srManager.deviceService.getDevices()) {
if (device.id() != rootDevice) {
- sBuilder.append("Paths from" + rootDevice + " to " + device.id() + "\r\n");
+ sBuilder.append("\r\n Paths from " + rootDevice + " to "
+ + device.id() + "\r\n");
ArrayList<Path> paths = getECMPPaths(device.id());
if (paths != null) {
for (Path path : paths) {
+ sBuilder.append("\r\n == "); // equal cost paths delimiter
for (Link link : path.links()) {
sBuilder.append(" : " + link.src() + " -> " + link.dst());
}