blob: 2bacf07747f4629bc784af29fbfec4f599d881b6 [file] [log] [blame]
Jonathan Hart96c146b2017-02-24 16:32:00 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Jonathan Hart96c146b2017-02-24 16:32:00 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Ray Milkey69ec8712017-08-08 13:00:43 -070017package org.onosproject.routeservice.store;
Jonathan Hart96c146b2017-02-24 16:32:00 -080018
Jordan Halterman3b137372018-04-30 14:42:41 -070019import java.util.Collection;
20import java.util.Collections;
piere91c87f2019-10-16 16:58:20 +020021import java.util.HashMap;
Jordan Halterman3b137372018-04-30 14:42:41 -070022import java.util.Map;
pier594843a2018-12-21 22:02:25 +010023import java.util.Objects;
Jordan Halterman3b137372018-04-30 14:42:41 -070024import java.util.concurrent.ExecutorService;
25import java.util.function.Consumer;
26import java.util.stream.Collectors;
27
Daniel Ginsburg83b76452018-06-09 01:43:59 +030028import com.google.common.collect.Sets;
Jonathan Hart96c146b2017-02-24 16:32:00 -080029import org.onlab.packet.IpAddress;
30import org.onlab.packet.IpPrefix;
Jordan Halterman5f5ceb62018-10-01 23:17:57 -070031import org.onosproject.cluster.NodeId;
Jonathan Hart96c146b2017-02-24 16:32:00 -080032import org.onlab.util.KryoNamespace;
Ray Milkey69ec8712017-08-08 13:00:43 -070033import org.onosproject.routeservice.InternalRouteEvent;
34import org.onosproject.routeservice.Route;
35import org.onosproject.routeservice.RouteSet;
36import org.onosproject.routeservice.RouteStoreDelegate;
37import org.onosproject.routeservice.RouteTableId;
Jonathan Hart96c146b2017-02-24 16:32:00 -080038import org.onosproject.store.serializers.KryoNamespaces;
Jordan Halterman3b137372018-04-30 14:42:41 -070039import org.onosproject.store.service.ConsistentMultimap;
Jonathan Hart1f67d282017-05-25 14:23:01 -070040import org.onosproject.store.service.DistributedPrimitive;
Jordan Halterman3b137372018-04-30 14:42:41 -070041import org.onosproject.store.service.MultimapEvent;
42import org.onosproject.store.service.MultimapEventListener;
Jonathan Hart96c146b2017-02-24 16:32:00 -080043import org.onosproject.store.service.Serializer;
44import org.onosproject.store.service.StorageService;
45import org.onosproject.store.service.Versioned;
46
piere91c87f2019-10-16 16:58:20 +020047
pier594843a2018-12-21 22:02:25 +010048import static com.google.common.base.MoreObjects.toStringHelper;
Jonathan Hart96c146b2017-02-24 16:32:00 -080049import static com.google.common.base.Preconditions.checkNotNull;
50
51/**
52 * Default implementation of a route table based on a consistent map.
53 */
54public class DefaultRouteTable implements RouteTable {
55
56 private final RouteTableId id;
Jordan Halterman5f5ceb62018-10-01 23:17:57 -070057
58 // The route map stores RawRoute instead of Route to translate the polymorphic IpPrefix and IpAddress types
59 // into monomorphic types (specifically String). Using strings in the stored RawRoute is necessary to ensure
60 // the serialized bytes are consistent whether e.g. IpAddress or Ip4Address is used when storing a route.
61 private final ConsistentMultimap<String, RawRoute> routes;
62
Jonathan Hart96c146b2017-02-24 16:32:00 -080063 private final RouteStoreDelegate delegate;
Jonathan Hart1f67d282017-05-25 14:23:01 -070064 private final ExecutorService executor;
Jonathan Hart96c146b2017-02-24 16:32:00 -080065 private final RouteTableListener listener = new RouteTableListener();
66
Jonathan Hart1f67d282017-05-25 14:23:01 -070067 private final Consumer<DistributedPrimitive.Status> statusChangeListener;
68
Jonathan Hart96c146b2017-02-24 16:32:00 -080069 /**
70 * Creates a new route table.
71 *
72 * @param id route table ID
73 * @param delegate route store delegate to notify of events
74 * @param storageService storage service
Jonathan Hart1f67d282017-05-25 14:23:01 -070075 * @param executor executor service
Jonathan Hart96c146b2017-02-24 16:32:00 -080076 */
77 public DefaultRouteTable(RouteTableId id, RouteStoreDelegate delegate,
Jonathan Hart1f67d282017-05-25 14:23:01 -070078 StorageService storageService, ExecutorService executor) {
Jonathan Hart96c146b2017-02-24 16:32:00 -080079 this.delegate = checkNotNull(delegate);
80 this.id = checkNotNull(id);
81 this.routes = buildRouteMap(checkNotNull(storageService));
Jonathan Hart1f67d282017-05-25 14:23:01 -070082 this.executor = checkNotNull(executor);
Jonathan Hart96c146b2017-02-24 16:32:00 -080083
Jonathan Hart1f67d282017-05-25 14:23:01 -070084 statusChangeListener = status -> {
85 if (status.equals(DistributedPrimitive.Status.ACTIVE)) {
86 executor.execute(this::notifyExistingRoutes);
87 }
88 };
89 routes.addStatusChangeListener(statusChangeListener);
90
91 notifyExistingRoutes();
92
Jordan Halterman3b137372018-04-30 14:42:41 -070093 routes.addListener(listener, executor);
Jonathan Hart1f67d282017-05-25 14:23:01 -070094 }
95
96 private void notifyExistingRoutes() {
Jordan Halterman3b137372018-04-30 14:42:41 -070097 getRoutes().forEach(routeSet -> delegate.notify(
98 new InternalRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, routeSet)));
Jonathan Hart96c146b2017-02-24 16:32:00 -080099 }
100
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700101 private ConsistentMultimap<String, RawRoute> buildRouteMap(StorageService storageService) {
Jonathan Hart96c146b2017-02-24 16:32:00 -0800102 KryoNamespace routeTableSerializer = KryoNamespace.newBuilder()
103 .register(KryoNamespaces.API)
104 .register(Route.class)
105 .register(Route.Source.class)
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700106 .register(RawRoute.class)
Jonathan Hart96c146b2017-02-24 16:32:00 -0800107 .build();
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700108 return storageService.<String, RawRoute>consistentMultimapBuilder()
Jonathan Hart96c146b2017-02-24 16:32:00 -0800109 .withName("onos-routes-" + id.name())
110 .withRelaxedReadConsistency()
111 .withSerializer(Serializer.using(routeTableSerializer))
112 .build();
113 }
114
115 @Override
116 public RouteTableId id() {
117 return id;
118 }
119
120 @Override
121 public void shutdown() {
Jonathan Hart1f67d282017-05-25 14:23:01 -0700122 routes.removeStatusChangeListener(statusChangeListener);
Jonathan Hart96c146b2017-02-24 16:32:00 -0800123 routes.removeListener(listener);
124 }
125
126 @Override
127 public void destroy() {
128 shutdown();
129 routes.destroy();
130 }
131
132 @Override
133 public void update(Route route) {
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700134 routes.put(route.prefix().toString(), new RawRoute(route));
Jonathan Hart96c146b2017-02-24 16:32:00 -0800135 }
136
137 @Override
piere91c87f2019-10-16 16:58:20 +0200138 public void update(Collection<Route> routesAdded) {
139 Map<String, Collection<? extends RawRoute>> computedRoutes = new HashMap<>();
140 computeRoutesToAdd(routesAdded).forEach((prefix, routes) -> computedRoutes.computeIfAbsent(
141 prefix, k -> Sets.newHashSet(routes)));
142 routes.putAll(computedRoutes);
143 }
144
145 @Override
Jonathan Hart96c146b2017-02-24 16:32:00 -0800146 public void remove(Route route) {
Jordan Haltermanfd822362018-12-22 17:01:18 -0800147 getRoutes(route.prefix())
148 .routes()
149 .stream()
150 .filter(r -> r.equals(route))
151 .findAny()
152 .ifPresent(matchRoute -> {
153 routes.remove(matchRoute.prefix().toString(), new RawRoute(matchRoute));
154 });
Jonathan Hart96c146b2017-02-24 16:32:00 -0800155 }
156
157 @Override
piere91c87f2019-10-16 16:58:20 +0200158 public void remove(Collection<Route> routesRemoved) {
159 Map<String, Collection<? extends RawRoute>> computedRoutes = new HashMap<>();
160 computeRoutesToRemove(routesRemoved).forEach((prefix, routes) -> computedRoutes.computeIfAbsent(
161 prefix, k -> Sets.newHashSet(routes)));
162 routes.removeAll(computedRoutes);
163 }
164
165 @Override
Daniel Ginsburg83b76452018-06-09 01:43:59 +0300166 public void replace(Route route) {
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700167 routes.replaceValues(route.prefix().toString(), Sets.newHashSet(new RawRoute(route)));
Daniel Ginsburg83b76452018-06-09 01:43:59 +0300168 }
169
170 @Override
Jonathan Hart96c146b2017-02-24 16:32:00 -0800171 public Collection<RouteSet> getRoutes() {
Jordan Halterman3b137372018-04-30 14:42:41 -0700172 return routes.stream()
173 .map(Map.Entry::getValue)
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700174 .collect(Collectors.groupingBy(RawRoute::prefix))
Jordan Halterman3b137372018-04-30 14:42:41 -0700175 .entrySet()
176 .stream()
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700177 .map(entry -> new RouteSet(id,
178 IpPrefix.valueOf(entry.getKey()),
179 entry.getValue().stream().map(RawRoute::route).collect(Collectors.toSet())))
Jordan Halterman3b137372018-04-30 14:42:41 -0700180 .collect(Collectors.toList());
Jonathan Hart96c146b2017-02-24 16:32:00 -0800181 }
182
183 @Override
184 public RouteSet getRoutes(IpPrefix prefix) {
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700185 Versioned<Collection<? extends RawRoute>> routeSet = routes.get(prefix.toString());
Jonathan Hart96c146b2017-02-24 16:32:00 -0800186 if (routeSet != null) {
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700187 return new RouteSet(id, prefix, routeSet.value().stream().map(RawRoute::route).collect(Collectors.toSet()));
Jonathan Hart96c146b2017-02-24 16:32:00 -0800188 }
189 return null;
190 }
191
192 @Override
193 public Collection<Route> getRoutesForNextHop(IpAddress nextHop) {
Jordan Halterman3b137372018-04-30 14:42:41 -0700194 return routes.stream()
195 .map(Map.Entry::getValue)
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700196 .filter(r -> IpAddress.valueOf(r.nextHop()).equals(nextHop))
197 .map(RawRoute::route)
Jordan Halterman3b137372018-04-30 14:42:41 -0700198 .collect(Collectors.toSet());
Jonathan Hart96c146b2017-02-24 16:32:00 -0800199 }
200
piere91c87f2019-10-16 16:58:20 +0200201 @Override
202 public Collection<RouteSet> getRoutesForNextHops(Collection<IpAddress> nextHops) {
203 // First create a reduced snapshot of the store iterating one time the map
204 Map<String, Collection<? extends RawRoute>> filteredRouteStore = new HashMap<>();
Charles Chan8eadbff2021-06-07 16:08:43 -0700205 routes.stream()
206 .map(Map.Entry::getValue)
piere91c87f2019-10-16 16:58:20 +0200207 .filter(r -> nextHops.contains(IpAddress.valueOf(r.nextHop())))
208 .forEach(r -> filteredRouteStore.computeIfAbsent(r.prefix, k -> {
209 // We need to get all the routes because the resolve logic
210 // will use the alternatives as well
211 Versioned<Collection<? extends RawRoute>> routeSet = routes.get(k);
212 if (routeSet != null) {
213 return routeSet.value();
214 }
215 return null;
216 }));
217 // Return the collection of the routeSet we have to resolve
218 return filteredRouteStore.entrySet().stream()
219 .map(entry -> new RouteSet(id, IpPrefix.valueOf(entry.getKey()),
220 entry.getValue().stream().map(RawRoute::route).collect(Collectors.toSet())))
221 .collect(Collectors.toSet());
222 }
223
224 private Map<String, Collection<RawRoute>> computeRoutesToAdd(Collection<Route> routesAdded) {
225 Map<String, Collection<RawRoute>> computedRoutes = new HashMap<>();
226 routesAdded.forEach(route -> {
227 Collection<RawRoute> tempRoutes = computedRoutes.computeIfAbsent(
228 route.prefix().toString(), k -> Sets.newHashSet());
229 tempRoutes.add(new RawRoute(route));
230 });
231 return computedRoutes;
232 }
233
234 private Map<String, Collection<RawRoute>> computeRoutesToRemove(Collection<Route> routesRemoved) {
235 Map<String, Collection<RawRoute>> computedRoutes = new HashMap<>();
236 routesRemoved.forEach(route -> getRoutes(route.prefix())
237 .routes()
238 .stream()
239 .filter(r -> r.equals(route))
240 .findAny()
241 .ifPresent(matchRoute -> {
242 Collection<RawRoute> tempRoutes = computedRoutes.computeIfAbsent(
243 matchRoute.prefix().toString(), k -> Sets.newHashSet());
244 tempRoutes.add(new RawRoute(matchRoute));
245 }));
246 return computedRoutes;
247 }
248
Jonathan Hart96c146b2017-02-24 16:32:00 -0800249 private class RouteTableListener
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700250 implements MultimapEventListener<String, RawRoute> {
Jonathan Hart96c146b2017-02-24 16:32:00 -0800251
252 private InternalRouteEvent createRouteEvent(
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700253 InternalRouteEvent.Type type, MultimapEvent<String, RawRoute> event) {
254 Collection<? extends RawRoute> currentRoutes = Versioned.valueOrNull(routes.get(event.key()));
Jordan Halterman3b137372018-04-30 14:42:41 -0700255 return new InternalRouteEvent(type, new RouteSet(
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700256 id, IpPrefix.valueOf(event.key()), currentRoutes != null ?
257 currentRoutes.stream().map(RawRoute::route).collect(Collectors.toSet())
258 : Collections.emptySet()));
Jonathan Hart96c146b2017-02-24 16:32:00 -0800259 }
260
261 @Override
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700262 public void event(MultimapEvent<String, RawRoute> event) {
Jonathan Hart96c146b2017-02-24 16:32:00 -0800263 InternalRouteEvent ire = null;
264 switch (event.type()) {
265 case INSERT:
266 ire = createRouteEvent(InternalRouteEvent.Type.ROUTE_ADDED, event);
267 break;
Jonathan Hart96c146b2017-02-24 16:32:00 -0800268 case REMOVE:
269 ire = createRouteEvent(InternalRouteEvent.Type.ROUTE_REMOVED, event);
270 break;
271 default:
272 break;
273 }
Jordan Halterman3b137372018-04-30 14:42:41 -0700274 delegate.notify(ire);
Jonathan Hart96c146b2017-02-24 16:32:00 -0800275 }
276 }
Jonathan Hart1f67d282017-05-25 14:23:01 -0700277
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700278 /**
279 * Represents a route object stored in the underlying ConsistentMultimap.
280 */
281 private static class RawRoute {
282 private Route.Source source;
283 private String prefix;
284 private String nextHop;
285 private NodeId sourceNode;
286
287 RawRoute(Route route) {
288 this.source = route.source();
289 this.prefix = route.prefix().toString();
290 this.nextHop = route.nextHop().toString();
291 this.sourceNode = route.sourceNode();
292 }
293
294 String prefix() {
295 return prefix;
296 }
297
298 String nextHop() {
299 return nextHop;
300 }
301
302 Route route() {
303 return new Route(source, IpPrefix.valueOf(prefix), IpAddress.valueOf(nextHop), sourceNode);
304 }
pier594843a2018-12-21 22:02:25 +0100305
306 public int hashCode() {
307 return Objects.hash(prefix, nextHop);
308 }
309
310 @Override
311 public boolean equals(Object other) {
312 if (this == other) {
313 return true;
314 }
315
316 if (!(other instanceof RawRoute)) {
317 return false;
318 }
319
320 RawRoute that = (RawRoute) other;
321
322 return Objects.equals(this.prefix, that.prefix) &&
323 Objects.equals(this.nextHop, that.nextHop);
324 }
325
326 @Override
327 public String toString() {
328 return toStringHelper(this)
329 .add("prefix", prefix)
330 .add("nextHop", nextHop)
331 .toString();
332 }
333
Jordan Halterman5f5ceb62018-10-01 23:17:57 -0700334 }
Jonathan Hart96c146b2017-02-24 16:32:00 -0800335}