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