Enhance the RouteEvent to notify listeners of alternative viable routes.
Traditionally the route event only notified listeners of the best selected
route for a given prefix, but some listeners are interested in all resolved
routes for the prefix.
CORD-905
Change-Id: Ia3e1e3a8e3e825ba894e6835e0860c3ed698d29b
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java
index ef56145..eceed5d 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java
@@ -19,6 +19,8 @@
import org.joda.time.LocalDateTime;
import org.onosproject.event.AbstractEvent;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
@@ -29,6 +31,7 @@
public class RouteEvent extends AbstractEvent<RouteEvent.Type, ResolvedRoute> {
private final ResolvedRoute prevSubject;
+ private final Collection<ResolvedRoute> alternativeRoutes;
/**
* Route event type.
@@ -40,6 +43,7 @@
* <p>
* The subject of this event should be the route being added.
* The prevSubject of this event should be null.
+ * </p>
*/
ROUTE_ADDED,
@@ -48,6 +52,7 @@
* <p>
* The subject of this event should be the new route.
* The prevSubject of this event should be the old route.
+ * </p>
*/
ROUTE_UPDATED,
@@ -56,8 +61,21 @@
* <p>
* The subject of this event should be the route being removed.
* The prevSubject of this event should be null.
+ * </p>
*/
- ROUTE_REMOVED
+ ROUTE_REMOVED,
+
+ /**
+ * The set of alternative routes for the subject's prefix has changed,
+ * but the best route is still the same.
+ * <p>
+ * The subject is the best route for the prefix (which has already been
+ * notified in a previous event).
+ * The prevSubject of this event is null.
+ * The alternatives contains the new set of alternative routes.
+ * </p>
+ */
+ ALTERNATIVE_ROUTES_CHANGED
}
/**
@@ -67,8 +85,18 @@
* @param subject event subject
*/
public RouteEvent(Type type, ResolvedRoute subject) {
- super(type, subject);
- this.prevSubject = null;
+ this(type, subject, null, Collections.emptySet());
+ }
+
+ /**
+ * Creates a new route event without specifying previous subject.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param alternatives alternative routes for subject's prefix
+ */
+ public RouteEvent(Type type, ResolvedRoute subject, Collection<ResolvedRoute> alternatives) {
+ this(type, subject, null, alternatives);
}
/**
@@ -81,6 +109,8 @@
protected RouteEvent(Type type, ResolvedRoute subject, long time) {
super(type, subject, time);
this.prevSubject = null;
+
+ this.alternativeRoutes = Collections.emptySet();
}
/**
@@ -91,8 +121,22 @@
* @param prevSubject previous subject
*/
public RouteEvent(Type type, ResolvedRoute subject, ResolvedRoute prevSubject) {
+ this(type, subject, prevSubject, Collections.emptySet());
+ }
+
+ /**
+ * Creates a new route event with a previous subject and alternative routes.
+ *
+ * @param type event type
+ * @param subject event subject
+ * @param prevSubject previous subject
+ * @param alternatives alternative routes for subject's prefix
+ */
+ public RouteEvent(Type type, ResolvedRoute subject, ResolvedRoute prevSubject,
+ Collection<ResolvedRoute> alternatives) {
super(type, subject);
this.prevSubject = prevSubject;
+ this.alternativeRoutes = alternatives;
}
/**
@@ -104,9 +148,18 @@
return prevSubject;
}
+ /**
+ * Returns the set of alternative routes for the subject's prefix.
+ *
+ * @return alternative routes
+ */
+ public Collection<ResolvedRoute> alternatives() {
+ return alternativeRoutes;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(subject(), type(), prevSubject());
+ return Objects.hash(subject(), type(), prevSubject(), alternativeRoutes);
}
@Override
@@ -123,7 +176,8 @@
return Objects.equals(this.subject(), that.subject()) &&
Objects.equals(this.type(), that.type()) &&
- Objects.equals(this.prevSubject(), that.prevSubject());
+ Objects.equals(this.prevSubject, that.prevSubject) &&
+ Objects.equals(this.alternativeRoutes, that.alternativeRoutes);
}
@Override
@@ -132,7 +186,8 @@
.add("time", new LocalDateTime(time()))
.add("type", type())
.add("subject", subject())
- .add("prevSubject", prevSubject())
+ .add("prevSubject", prevSubject)
+ .add("alternatives", alternativeRoutes)
.toString();
}
}
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/DefaultResolvedRouteStore.java b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/DefaultResolvedRouteStore.java
index d3f97b9..fa07d14 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/DefaultResolvedRouteStore.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/DefaultResolvedRouteStore.java
@@ -16,6 +16,8 @@
package org.onosproject.incubator.net.routing.impl;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import com.googlecode.concurrenttrees.common.KeyValuePair;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
@@ -57,8 +59,8 @@
}
@Override
- public RouteEvent updateRoute(ResolvedRoute route) {
- return getDefaultRouteTable(route).update(route);
+ public RouteEvent updateRoute(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ return getDefaultRouteTable(route).update(route, alternatives);
}
@Override
@@ -87,6 +89,11 @@
}
@Override
+ public Collection<ResolvedRoute> getAllRoutes(IpPrefix prefix) {
+ return getDefaultRouteTable(prefix.address()).getAllRoutes(prefix);
+ }
+
+ @Override
public Optional<ResolvedRoute> longestPrefixMatch(IpAddress ip) {
return getDefaultRouteTable(ip).longestPrefixMatch(ip);
}
@@ -105,6 +112,7 @@
*/
private class RouteTable {
private final InvertedRadixTree<ResolvedRoute> routeTable;
+ private final Map<IpPrefix, Set<ResolvedRoute>> alternativeRoutes;
/**
* Creates a new route table.
@@ -112,27 +120,58 @@
public RouteTable() {
routeTable = new ConcurrentInvertedRadixTree<>(
new DefaultByteArrayNodeFactory());
+
+ alternativeRoutes = Maps.newHashMap();
}
/**
* Adds or updates the route in the route table.
*
* @param route route to update
+ * @param alternatives alternative routes
*/
- public RouteEvent update(ResolvedRoute route) {
+ public RouteEvent update(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ Set<ResolvedRoute> immutableAlternatives = checkAlternatives(route, alternatives);
+
synchronized (this) {
ResolvedRoute oldRoute = routeTable.put(createBinaryString(route.prefix()), route);
+ Set<ResolvedRoute> oldRoutes = alternativeRoutes.put(route.prefix(), immutableAlternatives);
- // No need to proceed if the new route is the same
- if (route.equals(oldRoute)) {
- return null;
+ if (!route.equals(oldRoute)) {
+ if (oldRoute == null) {
+ return new RouteEvent(RouteEvent.Type.ROUTE_ADDED, route,
+ immutableAlternatives);
+ } else {
+ return new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, route,
+ oldRoute, immutableAlternatives);
+ }
}
- if (oldRoute == null) {
- return new RouteEvent(RouteEvent.Type.ROUTE_ADDED, route);
- } else {
- return new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, route, oldRoute);
+ if (!immutableAlternatives.equals(oldRoutes)) {
+ return new RouteEvent(RouteEvent.Type.ALTERNATIVE_ROUTES_CHANGED,
+ route, immutableAlternatives);
}
+
+ return null;
+ }
+ }
+
+ /**
+ * Checks that the best route is present in the alternatives list and
+ * returns an immutable set of alternatives.
+ *
+ * @param route best route
+ * @param alternatives alternatives
+ * @return immutable set of alternative routes
+ */
+ private Set<ResolvedRoute> checkAlternatives(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ if (!alternatives.contains(route)) {
+ return ImmutableSet.<ResolvedRoute>builder()
+ .addAll(alternatives)
+ .add(route)
+ .build();
+ } else {
+ return ImmutableSet.copyOf(alternatives);
}
}
@@ -146,6 +185,7 @@
String key = createBinaryString(prefix);
ResolvedRoute route = routeTable.getValueForExactKey(key);
+ alternativeRoutes.remove(prefix);
if (route != null) {
routeTable.remove(key);
@@ -176,6 +216,10 @@
return Optional.ofNullable(routeTable.getValueForExactKey(createBinaryString(prefix)));
}
+ public Collection<ResolvedRoute> getAllRoutes(IpPrefix prefix) {
+ return alternativeRoutes.getOrDefault(prefix, Collections.emptySet());
+ }
+
/**
* Performs a longest prefix match with the given IP in the route table.
*
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/ResolvedRouteStore.java b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/ResolvedRouteStore.java
index 55ca3f9..2285572 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/ResolvedRouteStore.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/ResolvedRouteStore.java
@@ -35,9 +35,10 @@
* Adds or updates the best route for the given prefix.
*
* @param route new best route for this prefix
+ * @param alternatives alternative resolved routes
* @return event describing the change
*/
- RouteEvent updateRoute(ResolvedRoute route);
+ RouteEvent updateRoute(ResolvedRoute route, Set<ResolvedRoute> alternatives);
/**
* Removes the best route for the given prefix.
@@ -71,6 +72,15 @@
Optional<ResolvedRoute> getRoute(IpPrefix prefix);
/**
+ * Returns all resolved routes stored for the given prefix, including the
+ * best selected route.
+ *
+ * @param prefix IP prefix to look up routes for
+ * @return all stored resolved routes for this prefix
+ */
+ Collection<ResolvedRoute> getAllRoutes(IpPrefix prefix);
+
+ /**
* Performs a longest prefix match of the best routes on the given IP address.
*
* @param ip IP address
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
index 5b637b8..539b2cb 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
@@ -23,7 +23,7 @@
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.IpAddress;
-import org.onosproject.event.ListenerService;
+import org.onlab.packet.IpPrefix;
import org.onosproject.incubator.net.routing.InternalRouteEvent;
import org.onosproject.incubator.net.routing.NextHop;
import org.onosproject.incubator.net.routing.ResolvedRoute;
@@ -68,8 +68,7 @@
*/
@Service
@Component
-public class RouteManager implements ListenerService<RouteEvent, RouteListener>,
- RouteService, RouteAdminService {
+public class RouteManager implements RouteService, RouteAdminService {
private final Logger log = LoggerFactory.getLogger(getClass());
@@ -129,7 +128,8 @@
resolvedRouteStore.getRouteTables().stream()
.map(resolvedRouteStore::getRoutes)
.flatMap(Collection::stream)
- .map(route -> new RouteEvent(RouteEvent.Type.ROUTE_ADDED, route))
+ .map(route -> new RouteEvent(RouteEvent.Type.ROUTE_ADDED, route,
+ resolvedRouteStore.getAllRoutes(route.prefix())))
.forEach(l::post);
listeners.put(listener, l);
@@ -259,25 +259,31 @@
}
private ResolvedRoute decide(ResolvedRoute route1, ResolvedRoute route2) {
- return Comparator.<ResolvedRoute, IpAddress>comparing(route -> route.nextHop())
+ return Comparator.comparing(ResolvedRoute::nextHop)
.compare(route1, route2) <= 0 ? route1 : route2;
}
- private void store(ResolvedRoute route) {
- post(resolvedRouteStore.updateRoute(route));
+ private void store(ResolvedRoute route, Set<ResolvedRoute> alternatives) {
+ post(resolvedRouteStore.updateRoute(route, alternatives));
+ }
+
+ private void remove(IpPrefix prefix) {
+ post(resolvedRouteStore.removeRoute(prefix));
}
private void resolve(RouteSet routes) {
- Optional<ResolvedRoute> resolvedRoute =
- routes.routes().stream()
- .map(this::resolve)
- .filter(Objects::nonNull)
+ Set<ResolvedRoute> resolvedRoutes = routes.routes().stream()
+ .map(this::resolve)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ Optional<ResolvedRoute> bestRoute = resolvedRoutes.stream()
.reduce(this::decide);
- if (resolvedRoute.isPresent()) {
- this.store(resolvedRoute.get());
+ if (bestRoute.isPresent()) {
+ store(bestRoute.get(), resolvedRoutes);
} else {
- post(resolvedRouteStore.removeRoute(routes.prefix()));
+ remove(routes.prefix());
}
}
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java
index aeb2ff1..370b83b 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java
@@ -199,7 +199,7 @@
private void verifyRouteAdd(Route route, ResolvedRoute resolvedRoute) {
reset(routeListener);
- routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_ADDED, resolvedRoute));
+ routeListener.event(event(RouteEvent.Type.ROUTE_ADDED, resolvedRoute));
replay(routeListener);
@@ -253,7 +253,7 @@
// First add the original route
addRoute(original);
- routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
+ routeListener.event(event(RouteEvent.Type.ROUTE_UPDATED,
updatedResolvedRoute, resolvedRoute));
expectLastCall().once();
@@ -329,7 +329,7 @@
// Now when we send the event, we expect the FIB update to be sent
reset(routeListener);
- routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_ADDED,
+ routeListener.event(event(RouteEvent.Type.ROUTE_ADDED,
new ResolvedRoute(route, MAC1, CP1)));
replay(routeListener);
@@ -349,6 +349,14 @@
verify(routeListener);
}
+ private static RouteEvent event(RouteEvent.Type type, ResolvedRoute subject) {
+ return event(type, subject, null);
+ }
+
+ private static RouteEvent event(RouteEvent.Type type, ResolvedRoute subject, ResolvedRoute prevSubject) {
+ return new RouteEvent(type, subject, prevSubject, Collections.singleton(subject));
+ }
+
/**
* Test host service that stores a reference to the host listener.
*/