Normalize route objects stored in RouteTable ConsistentMultimap to avoid inconsistent serialization for different IpPrefix/IpAddress types

Change-Id: I3e792fb92afdf388d2eaec5bd04dc47347f910f5
(cherry picked from commit 30ffafd7883b752fbc579582f6de5dc2e3c829d1)
This commit is contained in:
Jordan Halterman 2018-10-01 23:17:57 -07:00 committed by Charles Chan
parent 1c5f5f4592
commit 5f5ceb66a4

View File

@ -23,10 +23,10 @@ import java.util.concurrent.ExecutorService;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.onlab.packet.IpAddress; import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix; import org.onlab.packet.IpPrefix;
import org.onosproject.cluster.NodeId;
import org.onlab.util.KryoNamespace; import org.onlab.util.KryoNamespace;
import org.onosproject.routeservice.InternalRouteEvent; import org.onosproject.routeservice.InternalRouteEvent;
import org.onosproject.routeservice.Route; import org.onosproject.routeservice.Route;
@ -50,7 +50,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class DefaultRouteTable implements RouteTable { public class DefaultRouteTable implements RouteTable {
private final RouteTableId id; private final RouteTableId id;
private final ConsistentMultimap<IpPrefix, Route> routes;
// The route map stores RawRoute instead of Route to translate the polymorphic IpPrefix and IpAddress types
// into monomorphic types (specifically String). Using strings in the stored RawRoute is necessary to ensure
// the serialized bytes are consistent whether e.g. IpAddress or Ip4Address is used when storing a route.
private final ConsistentMultimap<String, RawRoute> routes;
private final RouteStoreDelegate delegate; private final RouteStoreDelegate delegate;
private final ExecutorService executor; private final ExecutorService executor;
private final RouteTableListener listener = new RouteTableListener(); private final RouteTableListener listener = new RouteTableListener();
@ -89,13 +94,14 @@ public class DefaultRouteTable implements RouteTable {
new InternalRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, routeSet))); new InternalRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, routeSet)));
} }
private ConsistentMultimap<IpPrefix, Route> buildRouteMap(StorageService storageService) { private ConsistentMultimap<String, RawRoute> buildRouteMap(StorageService storageService) {
KryoNamespace routeTableSerializer = KryoNamespace.newBuilder() KryoNamespace routeTableSerializer = KryoNamespace.newBuilder()
.register(KryoNamespaces.API) .register(KryoNamespaces.API)
.register(Route.class) .register(Route.class)
.register(Route.Source.class) .register(Route.Source.class)
.register(RawRoute.class)
.build(); .build();
return storageService.<IpPrefix, Route>consistentMultimapBuilder() return storageService.<String, RawRoute>consistentMultimapBuilder()
.withName("onos-routes-" + id.name()) .withName("onos-routes-" + id.name())
.withRelaxedReadConsistency() .withRelaxedReadConsistency()
.withSerializer(Serializer.using(routeTableSerializer)) .withSerializer(Serializer.using(routeTableSerializer))
@ -121,36 +127,37 @@ public class DefaultRouteTable implements RouteTable {
@Override @Override
public void update(Route route) { public void update(Route route) {
routes.put(route.prefix(), route); routes.put(route.prefix().toString(), new RawRoute(route));
} }
@Override @Override
public void remove(Route route) { public void remove(Route route) {
routes.remove(route.prefix(), route); routes.remove(route.prefix().toString(), new RawRoute(route));
} }
@Override @Override
public void replace(Route route) { public void replace(Route route) {
routes.replaceValues(route.prefix(), Sets.newHashSet(route)); routes.replaceValues(route.prefix().toString(), Sets.newHashSet(new RawRoute(route)));
} }
@Override @Override
public Collection<RouteSet> getRoutes() { public Collection<RouteSet> getRoutes() {
return routes.stream() return routes.stream()
.map(Map.Entry::getValue) .map(Map.Entry::getValue)
.collect(Collectors.groupingBy(Route::prefix)) .collect(Collectors.groupingBy(RawRoute::prefix))
.entrySet() .entrySet()
.stream() .stream()
.map(entry -> new RouteSet(id, entry.getKey(), ImmutableSet.copyOf(entry.getValue()))) .map(entry -> new RouteSet(id,
IpPrefix.valueOf(entry.getKey()),
entry.getValue().stream().map(RawRoute::route).collect(Collectors.toSet())))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override @Override
public RouteSet getRoutes(IpPrefix prefix) { public RouteSet getRoutes(IpPrefix prefix) {
Versioned<Collection<? extends Route>> routeSet = routes.get(prefix); Versioned<Collection<? extends RawRoute>> routeSet = routes.get(prefix.toString());
if (routeSet != null) { if (routeSet != null) {
return new RouteSet(id, prefix, ImmutableSet.copyOf(routeSet.value())); return new RouteSet(id, prefix, routeSet.value().stream().map(RawRoute::route).collect(Collectors.toSet()));
} }
return null; return null;
} }
@ -159,22 +166,25 @@ public class DefaultRouteTable implements RouteTable {
public Collection<Route> getRoutesForNextHop(IpAddress nextHop) { public Collection<Route> getRoutesForNextHop(IpAddress nextHop) {
return routes.stream() return routes.stream()
.map(Map.Entry::getValue) .map(Map.Entry::getValue)
.filter(r -> r.nextHop().equals(nextHop)) .filter(r -> IpAddress.valueOf(r.nextHop()).equals(nextHop))
.map(RawRoute::route)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
private class RouteTableListener private class RouteTableListener
implements MultimapEventListener<IpPrefix, Route> { implements MultimapEventListener<String, RawRoute> {
private InternalRouteEvent createRouteEvent( private InternalRouteEvent createRouteEvent(
InternalRouteEvent.Type type, MultimapEvent<IpPrefix, Route> event) { InternalRouteEvent.Type type, MultimapEvent<String, RawRoute> event) {
Collection<? extends Route> currentRoutes = Versioned.valueOrNull(routes.get(event.key())); Collection<? extends RawRoute> currentRoutes = Versioned.valueOrNull(routes.get(event.key()));
return new InternalRouteEvent(type, new RouteSet( return new InternalRouteEvent(type, new RouteSet(
id, event.key(), currentRoutes != null ? ImmutableSet.copyOf(currentRoutes) : Collections.emptySet())); id, IpPrefix.valueOf(event.key()), currentRoutes != null ?
currentRoutes.stream().map(RawRoute::route).collect(Collectors.toSet())
: Collections.emptySet()));
} }
@Override @Override
public void event(MultimapEvent<IpPrefix, Route> event) { public void event(MultimapEvent<String, RawRoute> event) {
InternalRouteEvent ire = null; InternalRouteEvent ire = null;
switch (event.type()) { switch (event.type()) {
case INSERT: case INSERT:
@ -190,4 +200,32 @@ public class DefaultRouteTable implements RouteTable {
} }
} }
/**
* Represents a route object stored in the underlying ConsistentMultimap.
*/
private static class RawRoute {
private Route.Source source;
private String prefix;
private String nextHop;
private NodeId sourceNode;
RawRoute(Route route) {
this.source = route.source();
this.prefix = route.prefix().toString();
this.nextHop = route.nextHop().toString();
this.sourceNode = route.sourceNode();
}
String prefix() {
return prefix;
}
String nextHop() {
return nextHop;
}
Route route() {
return new Route(source, IpPrefix.valueOf(prefix), IpAddress.valueOf(nextHop), sourceNode);
}
}
} }