CORD-637 Fixing intermittent filtering objective failures by repeatedly trying
till results are consistent. Also fixed some typos and made some logs clearer.
Change-Id: If829b02ac6dc2f8ada455b5290c718d29a6d7988
diff --git a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index 01ffd6e..ba918c6 100644
--- a/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.segmentrouting;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -37,6 +38,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -44,13 +46,15 @@
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.MoreObjects.toStringHelper;
/**
* Default routing handler that is responsible for route computing and
* routing rule population.
*/
public class DefaultRoutingHandler {
- private static final int MAX_RETRY_ATTEMPTS = 25;
+ private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 4;
+ private static final int RETRY_INTERVAL_MS = 500;
private static final String ECMPSPG_MISSING = "ECMP shortest path graph not found";
private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
@@ -62,7 +66,7 @@
private final Lock statusLock = new ReentrantLock();
private volatile Status populationStatus;
private ScheduledExecutorService executorService
- = newScheduledThreadPool(1, groupedThreads("RoutingHandler", "retry-%d", log));
+ = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
/**
* Represents the default routing population status.
@@ -612,10 +616,12 @@
// ports for this device yet. It results in missing filtering rules in the
// switch. We will attempt it a few times. If it still does not work,
// user can manually repopulate using CLI command sr-reroute-network
- boolean success = rulePopulator.populateRouterMacVlanFilters(deviceId);
- if (!success) {
- executorService.schedule(new RetryFilters(deviceId), 200, TimeUnit.MILLISECONDS);
+ PortFilterInfo firstRun = rulePopulator.populateRouterMacVlanFilters(deviceId);
+ if (firstRun == null) {
+ firstRun = new PortFilterInfo(0, 0, 0);
}
+ executorService.schedule(new RetryFilters(deviceId, firstRun),
+ RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
}
/**
@@ -704,26 +710,82 @@
this.populateRoutingRulesForLinkStatusChange(null);
}
- private final class RetryFilters implements Runnable {
- int attempts = MAX_RETRY_ATTEMPTS;
- DeviceId devId;
+ /**
+ * Utility class used to temporarily store information about the ports on a
+ * device processed for filtering objectives.
+ *
+ */
+ public final class PortFilterInfo {
+ int disabledPorts = 0, suppressedPorts = 0, filteredPorts = 0;
- private RetryFilters(DeviceId deviceId) {
+ public PortFilterInfo(int disabledPorts, int suppressedPorts,
+ int filteredPorts) {
+ this.disabledPorts = disabledPorts;
+ this.filteredPorts = filteredPorts;
+ this.suppressedPorts = suppressedPorts;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(disabledPorts, filteredPorts, suppressedPorts);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
+ return false;
+ }
+ PortFilterInfo other = (PortFilterInfo) obj;
+ return ((disabledPorts == other.disabledPorts) &&
+ (filteredPorts == other.filteredPorts) &&
+ (suppressedPorts == other.suppressedPorts));
+ }
+
+ @Override
+ public String toString() {
+ MoreObjects.ToStringHelper helper = toStringHelper(this)
+ .add("disabledPorts", disabledPorts)
+ .add("suppressedPorts", suppressedPorts)
+ .add("filteredPorts", filteredPorts);
+ return helper.toString();
+ }
+ }
+
+ /**
+ * RetryFilters populates filtering objectives for a device and keeps retrying
+ * till the number of ports filtered are constant for a predefined number
+ * of attempts.
+ */
+ protected final class RetryFilters implements Runnable {
+ int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
+ DeviceId devId;
+ int counter;
+ PortFilterInfo prevRun;
+
+ private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
devId = deviceId;
+ prevRun = previousRun;
+ counter = 0;
}
@Override
public void run() {
- log.info("RETRY FILTER ATTEMPT# {} for dev:{}",
- MAX_RETRY_ATTEMPTS - attempts, devId);
- boolean success = rulePopulator.populateRouterMacVlanFilters(devId);
- if (!success && --attempts > 0) {
- executorService.schedule(this, 200, TimeUnit.MILLISECONDS);
- } else if (attempts == 0) {
- log.error("Unable to populate MacVlan filters in dev:{}", devId);
+ log.info("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
+ PortFilterInfo thisRun = rulePopulator.populateRouterMacVlanFilters(devId);
+ boolean sameResult = prevRun.equals(thisRun);
+ log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
+ thisRun, sameResult);
+ if (thisRun == null || !sameResult || (sameResult && --constantAttempts > 0)) {
+ executorService.schedule(this, RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
+ if (!sameResult) {
+ constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
+ }
}
+ prevRun = (thisRun == null) ? prevRun : thisRun;
}
-
}
}
diff --git a/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java b/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java
index 5e7e2ae..f5d8ed1 100644
--- a/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java
+++ b/src/main/java/org/onosproject/segmentrouting/EcmpShortestPathGraph.java
@@ -31,7 +31,7 @@
/**
* This class creates bandwidth constrained breadth first tree and returns paths
- * from root Device to leaf Devices which satisfies the bandwidth condition. If
+ * from root Device to leaf Devices (target devices) which satisfies the bandwidth condition. If
* bandwidth parameter is not specified, the normal breadth first tree will be
* calculated. The paths are snapshot paths at the point of the class
* instantiation.
@@ -302,15 +302,15 @@
}
/**
- * Return the complete info of the computed ECMP paths for each Device
+ * Returns the complete info of the computed ECMP paths for each target 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),
+ * iteration, for the target devices reached (DeviceId key of inner HashMap),
* the ECMP paths are detailed (2D array).
*
- * @return the hash table of Devices learned in multiple Dijkstra
+ * @return the hash table of target Devices learned in multiple Dijkstra
* iterations and corresponding ECMP paths in terms of Devices to
- * be traversed to it from the root Device
+ * be traversed (via) from the root Device to the target Device
*/
public HashMap<Integer, HashMap<DeviceId,
ArrayList<ArrayList<DeviceId>>>> getAllLearnedSwitchesAndVia() {
diff --git a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index e12fbcc..2f07c05 100644
--- a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -26,6 +26,7 @@
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.flowobjective.DefaultObjectiveContext;
import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.segmentrouting.DefaultRoutingHandler.PortFilterInfo;
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.DeviceConfiguration;
import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
@@ -535,9 +536,9 @@
* that drivers can obtain other information (like Router MAC and IP).
*
* @param deviceId the switch dpid for the router
- * @return true if operation succeeds
+ * @return PortFilterInfo information about the processed ports
*/
- public boolean populateRouterMacVlanFilters(DeviceId deviceId) {
+ public PortFilterInfo populateRouterMacVlanFilters(DeviceId deviceId) {
log.debug("Installing per-port filtering objective for untagged "
+ "packets in device {}", deviceId);
@@ -546,14 +547,14 @@
deviceMac = config.getDeviceMac(deviceId);
} catch (DeviceConfigNotFoundException e) {
log.warn(e.getMessage() + " Aborting populateRouterMacVlanFilters.");
- return false;
+ return null;
}
List<Port> devPorts = srManager.deviceService.getPorts(deviceId);
if (devPorts != null && devPorts.size() == 0) {
log.warn("Device {} ports not available. Unable to add MacVlan filters",
deviceId);
- return false;
+ return null;
}
int disabledPorts = 0, suppressedPorts = 0, filteredPorts = 0;
for (Port port : devPorts) {
@@ -596,13 +597,8 @@
}
log.info("Filtering on dev:{}, disabledPorts:{}, suppressedPorts:{}, filteredPorts:{}",
deviceId, disabledPorts, suppressedPorts, filteredPorts);
- // XXX With this check, there is a chance that not all the ports that
- // should be filtered actually get filtered as long as one of them does.
- // Note there is no PORT_UPDATED event that makes the port go from disabled
- // to enabled state, because the ports comes enabled from the switch.
- // Check ONOS core, where the port becoming available and being declared
- // enabled is possibly not atomic.
- return (filteredPorts > 0) ? true : false;
+ return srManager.defaultRoutingHandler.new PortFilterInfo(disabledPorts,
+ suppressedPorts, filteredPorts);
}
/**
diff --git a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index ca41a0f..dd2f5e8 100644
--- a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -742,14 +742,17 @@
processDeviceRemoved((Device) event.subject());
}
} else if (event.type() == DeviceEvent.Type.PORT_ADDED) {
- // XXX typically these calls come when device is added
- // so port filtering rules are handled there, and it
- // represents all ports on the device, enabled or not.
+ // typically these calls come when device is added first time
+ // so port filtering rules are handled at the device_added event.
+ // port added calls represent all ports on the device,
+ // enabled or not.
log.debug("** PORT ADDED {}/{} -> {}",
- event.subject(),
- ((DeviceEvent) event).port(),
- event.type());
+ ((DeviceEvent) event).subject().id(),
+ ((DeviceEvent) event).port().number(),
+ event.type());
} else if (event.type() == DeviceEvent.Type.PORT_UPDATED) {
+ // these calls happen for every subsequent event
+ // ports enabled, disabled, switch goes away, comes back
log.info("** PORT UPDATED {}/{} -> {}",
event.subject(),
((DeviceEvent) event).port(),