blob: 98c299304fda0e7402bf1240f6d33b6c3723a491 [file] [log] [blame]
Charles Chan0214ded2016-11-18 17:48:37 -08001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
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
17package org.onosproject.incubator.store.routing.impl;
18
19import com.google.common.collect.Maps;
20import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
21import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
22import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
23import org.onlab.packet.IpAddress;
24import org.onlab.packet.IpPrefix;
25import org.onlab.util.KryoNamespace;
26import org.onosproject.incubator.net.routing.NextHopData;
27import org.onosproject.incubator.net.routing.ResolvedRoute;
28import org.onosproject.incubator.net.routing.Route;
29import org.onosproject.incubator.net.routing.RouteEvent;
30import org.onosproject.incubator.net.routing.RouteStore;
31import org.onosproject.incubator.net.routing.RouteStoreDelegate;
32import org.onosproject.incubator.net.routing.RouteTableId;
33import org.onosproject.store.AbstractStore;
34import org.onosproject.store.serializers.KryoNamespaces;
35import org.onosproject.store.service.ConsistentMap;
36import org.onosproject.store.service.MapEvent;
37import org.onosproject.store.service.MapEventListener;
38import org.onosproject.store.service.Serializer;
39import org.onosproject.store.service.StorageService;
40import org.onosproject.store.service.Versioned;
41import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43
44import java.util.Collection;
45import java.util.Collections;
46import java.util.Iterator;
47import java.util.Map;
48import java.util.Set;
49import java.util.concurrent.Executors;
50import java.util.stream.Collectors;
51
52import static com.google.common.base.Preconditions.checkNotNull;
53import static org.onosproject.incubator.net.routing.RouteEvent.Type.ROUTE_ADDED;
54import static org.onosproject.incubator.net.routing.RouteEvent.Type.ROUTE_REMOVED;
Jonathan Hartc4c2d622017-02-10 14:13:57 -080055import static org.onosproject.incubator.net.routing.RouteTools.createBinaryString;
Charles Chan0214ded2016-11-18 17:48:37 -080056
57/**
58 * Route store based on distributed storage.
59 */
60public class DistributedRouteStore extends AbstractStore<RouteEvent, RouteStoreDelegate>
61 implements RouteStore {
62 public StorageService storageService;
63
64 private static final RouteTableId IPV4 = new RouteTableId("ipv4");
65 private static final RouteTableId IPV6 = new RouteTableId("ipv6");
66 private static final Logger log = LoggerFactory.getLogger(DistributedRouteStore.class);
67 private final MapEventListener<IpPrefix, Route> routeTableListener = new RouteTableListener();
68 private final MapEventListener<IpAddress, NextHopData> nextHopListener = new NextHopListener();
69
70 // TODO: ConsistentMap may not scale with high frequency route update
71 private final Map<RouteTableId, ConsistentMap<IpPrefix, Route>> routeTables =
72 Maps.newHashMap();
73 // NOTE: We cache local route tables with InvertedRadixTree for longest prefix matching
74 private final Map<RouteTableId, InvertedRadixTree<Route>> localRouteTables =
75 Maps.newHashMap();
76 private ConsistentMap<IpAddress, NextHopData> nextHops;
77
78 /**
79 * Constructs a distributed route store.
80 *
81 * @param storageService storage service should be passed from RouteStoreImpl
82 */
83 public DistributedRouteStore(StorageService storageService) {
84 this.storageService = storageService;
85 }
86
87 /**
88 * Sets up distributed route store.
89 */
90 public void activate() {
91 // Creates and stores maps
92 ConsistentMap<IpPrefix, Route> ipv4RouteTable = createRouteTable(IPV4);
93 ConsistentMap<IpPrefix, Route> ipv6RouteTable = createRouteTable(IPV6);
94 routeTables.put(IPV4, ipv4RouteTable);
95 routeTables.put(IPV6, ipv6RouteTable);
96 localRouteTables.put(IPV4, createLocalRouteTable());
97 localRouteTables.put(IPV6, createLocalRouteTable());
98 nextHops = createNextHopTable();
99
100 // Adds map listeners
101 routeTables.values().forEach(routeTable ->
102 routeTable.addListener(routeTableListener, Executors.newSingleThreadExecutor()));
103 nextHops.addListener(nextHopListener, Executors.newSingleThreadExecutor());
104
105 log.info("Started");
106 }
107
108 /**
109 * Cleans up distributed route store.
110 */
111 public void deactivate() {
112 routeTables.values().forEach(routeTable -> {
113 routeTable.removeListener(routeTableListener);
114 routeTable.destroy();
115 });
116 nextHops.removeListener(nextHopListener);
117 nextHops.destroy();
118
119 routeTables.clear();
120 localRouteTables.clear();
121 nextHops.clear();
122
123 log.info("Stopped");
124 }
125
126 @Override
127 public void updateRoute(Route route) {
128 getDefaultRouteTable(route).put(route.prefix(), route);
129 }
130
131 @Override
132 public void removeRoute(Route route) {
133 getDefaultRouteTable(route).remove(route.prefix());
134
135 if (getRoutesForNextHop(route.nextHop()).isEmpty()) {
136 nextHops.remove(route.nextHop());
137 }
138 }
139
140 @Override
141 public Set<RouteTableId> getRouteTables() {
142 return routeTables.keySet();
143 }
144
145 @Override
146 public Collection<Route> getRoutes(RouteTableId table) {
147 ConsistentMap<IpPrefix, Route> routeTable = routeTables.get(table);
148 return (routeTable != null) ?
149 routeTable.values().stream().map(Versioned::value).collect(Collectors.toSet()) :
150 Collections.emptySet();
151 }
152
153 @Override
154 public Route longestPrefixMatch(IpAddress ip) {
155 Iterable<Route> prefixes = getDefaultLocalRouteTable(ip)
156 .getValuesForKeysPrefixing(createBinaryString(ip.toIpPrefix()));
157 Iterator<Route> it = prefixes.iterator();
158
159 Route route = null;
160 while (it.hasNext()) {
161 route = it.next();
162 }
163
164 return route;
165 }
166
167 @Override
168 public Collection<Route> getRoutesForNextHop(IpAddress ip) {
169 return getDefaultRouteTable(ip).values().stream()
170 .filter(route -> route.nextHop().equals(ip))
171 .collect(Collectors.toList());
172 }
173
174 @Override
175 public void updateNextHop(IpAddress ip, NextHopData nextHopData) {
176 checkNotNull(ip);
177 checkNotNull(nextHopData);
178 Collection<Route> routes = getRoutesForNextHop(ip);
179 if (!routes.isEmpty() && !nextHopData.equals(getNextHop(ip))) {
180 nextHops.put(ip, nextHopData);
181 }
182 }
183
184 @Override
185 public void removeNextHop(IpAddress ip, NextHopData nextHopData) {
186 checkNotNull(ip);
187 checkNotNull(nextHopData);
188 nextHops.remove(ip, nextHopData);
189 }
190
191 @Override
192 public NextHopData getNextHop(IpAddress ip) {
193 return Versioned.valueOrNull(nextHops.get(ip));
194 }
195
196 @Override
197 public Map<IpAddress, NextHopData> getNextHops() {
198 return nextHops.asJavaMap();
199 }
200
201 private ConsistentMap<IpPrefix, Route> createRouteTable(RouteTableId tableId) {
202 KryoNamespace routeTableSerializer = KryoNamespace.newBuilder()
203 .register(KryoNamespaces.API)
204 .register(Route.class)
205 .register(Route.Source.class)
206 .build();
207 return storageService.<IpPrefix, Route>consistentMapBuilder()
208 .withName("onos-routes-" + tableId.name())
209 .withRelaxedReadConsistency()
210 .withSerializer(Serializer.using(routeTableSerializer))
211 .build();
212 }
213
214 private ConcurrentInvertedRadixTree<Route> createLocalRouteTable() {
215 return new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
216 }
217
218 private ConsistentMap<IpAddress, NextHopData> createNextHopTable() {
219 KryoNamespace.Builder nextHopSerializer = KryoNamespace.newBuilder()
220 .register(KryoNamespaces.API)
221 .register(NextHopData.class);
222 return storageService.<IpAddress, NextHopData>consistentMapBuilder()
223 .withName("onos-nexthops")
224 .withRelaxedReadConsistency()
225 .withSerializer(Serializer.using(nextHopSerializer.build()))
226 .build();
227 }
228
229 private Map<IpPrefix, Route> getDefaultRouteTable(Route route) {
230 return getDefaultRouteTable(route.prefix().address());
231 }
232
233 private Map<IpPrefix, Route> getDefaultRouteTable(IpAddress ip) {
234 RouteTableId routeTableId = (ip.isIp4()) ? IPV4 : IPV6;
235 return routeTables.get(routeTableId).asJavaMap();
236 }
237
238 private InvertedRadixTree<Route> getDefaultLocalRouteTable(IpAddress ip) {
239 RouteTableId routeTableId = (ip.isIp4()) ? IPV4 : IPV6;
240 return localRouteTables.get(routeTableId);
241 }
242
Charles Chan0214ded2016-11-18 17:48:37 -0800243 private class RouteTableListener implements MapEventListener<IpPrefix, Route> {
244 @Override
245 public void event(MapEvent<IpPrefix, Route> event) {
246 Route route, prevRoute;
247 NextHopData nextHopData, prevNextHopData;
248 switch (event.type()) {
249 case INSERT:
250 route = checkNotNull(event.newValue().value());
251 nextHopData = getNextHop(route.nextHop());
252
253 // Update local cache
254 getDefaultLocalRouteTable(route.nextHop())
255 .put(createBinaryString(route.prefix()), route);
256
257 // Send ROUTE_ADDED only when the next hop is resolved
258 if (nextHopData != null) {
259 notifyDelegate(new RouteEvent(ROUTE_ADDED,
260 new ResolvedRoute(route,
261 nextHopData.mac(), nextHopData.location())));
262 }
263 break;
264 case UPDATE:
265 route = checkNotNull(event.newValue().value());
266 prevRoute = checkNotNull(event.oldValue().value());
267 nextHopData = getNextHop(route.nextHop());
268 prevNextHopData = getNextHop(prevRoute.nextHop());
269
270 // Update local cache
271 getDefaultLocalRouteTable(route.nextHop())
272 .put(createBinaryString(route.prefix()), route);
273
274 if (nextHopData == null && prevNextHopData != null) {
275 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
276 new ResolvedRoute(prevRoute,
277 prevNextHopData.mac(), prevNextHopData.location())));
278 } else if (nextHopData != null && prevNextHopData != null) {
279 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
280 new ResolvedRoute(route,
281 nextHopData.mac(), nextHopData.location()),
282 new ResolvedRoute(prevRoute,
283 prevNextHopData.mac(), prevNextHopData.location())));
284 }
285
286 cleanupNextHop(prevRoute.nextHop());
287 break;
288 case REMOVE:
289 prevRoute = checkNotNull(event.oldValue().value());
290 prevNextHopData = getNextHop(prevRoute.nextHop());
291
292 // Update local cache
293 getDefaultLocalRouteTable(prevRoute.nextHop())
294 .remove(createBinaryString(prevRoute.prefix()));
295
296 // Send ROUTE_REMOVED only when the next hop is resolved
297 if (prevNextHopData != null) {
298 notifyDelegate(new RouteEvent(ROUTE_REMOVED,
299 new ResolvedRoute(prevRoute,
300 prevNextHopData.mac(), prevNextHopData.location())));
301 }
302
303 cleanupNextHop(prevRoute.nextHop());
304 break;
305 default:
306 log.warn("Unknown MapEvent type: {}", event.type());
307 }
308 }
309
310 /**
311 * Cleanup a nexthop when there is no routes reference to it.
312 */
313 private void cleanupNextHop(IpAddress ip) {
314 if (getDefaultRouteTable(ip).values().stream().noneMatch(route ->
315 route.nextHop().equals(ip))) {
316 nextHops.remove(ip);
317 }
318 }
319 }
320
321 private class NextHopListener implements MapEventListener<IpAddress, NextHopData> {
322 @Override
323 public void event(MapEvent<IpAddress, NextHopData> event) {
324 NextHopData nextHopData, oldNextHopData;
325 Collection<Route> routes = getRoutesForNextHop(event.key());
326
327 switch (event.type()) {
328 case INSERT:
329 nextHopData = checkNotNull(event.newValue().value());
330 routes.forEach(route ->
331 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_ADDED,
332 new ResolvedRoute(route,
333 nextHopData.mac(), nextHopData.location())))
334 );
335 break;
336 case UPDATE:
337 nextHopData = checkNotNull(event.newValue().value());
338 oldNextHopData = checkNotNull(event.oldValue().value());
339 routes.forEach(route -> {
340 if (oldNextHopData == null) {
341 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_ADDED,
342 new ResolvedRoute(route,
343 nextHopData.mac(), nextHopData.location())));
344 } else if (!oldNextHopData.equals(nextHopData)) {
345 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
346 new ResolvedRoute(route,
347 nextHopData.mac(), nextHopData.location()),
348 new ResolvedRoute(route,
349 oldNextHopData.mac(), oldNextHopData.location())));
350 }
351 });
352 break;
353 case REMOVE:
354 oldNextHopData = checkNotNull(event.oldValue().value());
355 routes.forEach(route ->
356 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
357 new ResolvedRoute(route,
358 oldNextHopData.mac(), oldNextHopData.location())))
359 );
360 break;
361 default:
362 log.warn("Unknown MapEvent type: {}", event.type());
363 }
364 }
365 }
366}