blob: 5f3640baac512c305a8e7ddf45ddbe0f84be4893 [file] [log] [blame]
Jonathan Hartbfc5c482016-04-05 18:57:00 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Jonathan Hartbfc5c482016-04-05 18:57:00 -07003 *
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.net.routing.impl;
18
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Reference;
23import org.apache.felix.scr.annotations.ReferenceCardinality;
24import org.apache.felix.scr.annotations.Service;
25import org.onlab.packet.IpAddress;
Charles Chancab494d2016-11-11 16:31:49 -080026import org.onosproject.core.ApplicationId;
Jonathan Hartbfc5c482016-04-05 18:57:00 -070027import org.onosproject.event.ListenerService;
Charles Chan8fe9f4c2016-10-24 16:46:25 -070028import org.onosproject.incubator.net.routing.NextHopData;
Jonathan Hartfd176612016-04-11 10:42:10 -070029import org.onosproject.incubator.net.routing.NextHop;
Jonathan Hartbfc5c482016-04-05 18:57:00 -070030import org.onosproject.incubator.net.routing.ResolvedRoute;
31import org.onosproject.incubator.net.routing.Route;
32import org.onosproject.incubator.net.routing.RouteAdminService;
Charles Chancab494d2016-11-11 16:31:49 -080033import org.onosproject.incubator.net.routing.RouteConfig;
Jonathan Hartbfc5c482016-04-05 18:57:00 -070034import org.onosproject.incubator.net.routing.RouteEvent;
35import org.onosproject.incubator.net.routing.RouteListener;
36import org.onosproject.incubator.net.routing.RouteService;
37import org.onosproject.incubator.net.routing.RouteStore;
38import org.onosproject.incubator.net.routing.RouteStoreDelegate;
39import org.onosproject.incubator.net.routing.RouteTableId;
40import org.onosproject.net.Host;
Charles Chancab494d2016-11-11 16:31:49 -080041import org.onosproject.net.config.ConfigFactory;
42import org.onosproject.net.config.NetworkConfigEvent;
43import org.onosproject.net.config.NetworkConfigListener;
44import org.onosproject.net.config.NetworkConfigRegistry;
45import org.onosproject.net.config.basics.SubjectFactories;
Jonathan Hartbfc5c482016-04-05 18:57:00 -070046import org.onosproject.net.host.HostEvent;
47import org.onosproject.net.host.HostListener;
48import org.onosproject.net.host.HostService;
49import org.slf4j.Logger;
50import org.slf4j.LoggerFactory;
51
52import javax.annotation.concurrent.GuardedBy;
53import java.util.Collection;
54import java.util.Collections;
55import java.util.HashMap;
56import java.util.Map;
57import java.util.Optional;
58import java.util.Set;
59import java.util.concurrent.BlockingQueue;
60import java.util.concurrent.ExecutorService;
61import java.util.concurrent.LinkedBlockingQueue;
62import java.util.concurrent.ThreadFactory;
63import java.util.function.Function;
64import java.util.stream.Collectors;
65
66import static java.util.concurrent.Executors.newSingleThreadExecutor;
67import static org.onlab.util.Tools.groupedThreads;
68
69/**
70 * Implementation of the unicast route service.
71 */
72@Service
73@Component
74public class RouteManager implements ListenerService<RouteEvent, RouteListener>,
75 RouteService, RouteAdminService {
76
77 private final Logger log = LoggerFactory.getLogger(getClass());
78
79 private RouteStoreDelegate delegate = new InternalRouteStoreDelegate();
80 private InternalHostListener hostListener = new InternalHostListener();
81
82 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 protected RouteStore routeStore;
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 protected HostService hostService;
87
Charles Chancab494d2016-11-11 16:31:49 -080088 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected NetworkConfigRegistry netcfgRegistry;
90
Jonathan Hartbfc5c482016-04-05 18:57:00 -070091 @GuardedBy(value = "this")
92 private Map<RouteListener, ListenerQueue> listeners = new HashMap<>();
93
94 private ThreadFactory threadFactory;
95
Charles Chancab494d2016-11-11 16:31:49 -080096 private final ConfigFactory<ApplicationId, RouteConfig> routeConfigFactory =
97 new ConfigFactory<ApplicationId, RouteConfig>(
98 SubjectFactories.APP_SUBJECT_FACTORY,
99 RouteConfig.class, "routes", true) {
100 @Override
101 public RouteConfig createConfig() {
102 return new RouteConfig();
103 }
104 };
105 private final InternalNetworkConfigListener netcfgListener =
106 new InternalNetworkConfigListener();
107
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700108 @Activate
109 protected void activate() {
Yuta HIGUCHI1624df12016-07-21 16:54:33 -0700110 threadFactory = groupedThreads("onos/route", "listener-%d", log);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700111
112 routeStore.setDelegate(delegate);
113 hostService.addListener(hostListener);
Charles Chancab494d2016-11-11 16:31:49 -0800114 netcfgRegistry.addListener(netcfgListener);
115 netcfgRegistry.registerConfigFactory(routeConfigFactory);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700116 }
117
118 @Deactivate
119 protected void deactivate() {
120 listeners.values().forEach(l -> l.stop());
121
122 routeStore.unsetDelegate(delegate);
123 hostService.removeListener(hostListener);
Charles Chancab494d2016-11-11 16:31:49 -0800124 netcfgRegistry.removeListener(netcfgListener);
125 netcfgRegistry.unregisterConfigFactory(routeConfigFactory);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700126 }
127
128 /**
129 * {@inheritDoc}
130 *
131 * In a departure from other services in ONOS, calling addListener will
132 * cause all current routes to be pushed to the listener before any new
133 * events are sent. This allows a listener to easily get the exact set of
134 * routes without worrying about missing any.
135 *
136 * @param listener listener to be added
137 */
138 @Override
139 public void addListener(RouteListener listener) {
140 synchronized (this) {
141 log.debug("Synchronizing current routes to new listener");
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700142 ListenerQueue l = createListenerQueue(listener);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700143 routeStore.getRouteTables().forEach(table -> {
144 Collection<Route> routes = routeStore.getRoutes(table);
145 if (routes != null) {
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700146 routes.forEach(route -> {
147 NextHopData nextHopData = routeStore.getNextHop(route.nextHop());
Pier Luigia44d18a2017-01-13 14:00:23 -0800148 if (nextHopData != null) {
Charles Chane4d13102016-11-08 15:38:44 -0800149 l.post(new RouteEvent(RouteEvent.Type.ROUTE_ADDED,
Pier Luigia44d18a2017-01-13 14:00:23 -0800150 new ResolvedRoute(route, nextHopData.mac(),
151 nextHopData.location())));
152 }
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700153 });
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700154 }
155 });
156
157 listeners.put(listener, l);
158
159 l.start();
160 log.debug("Route synchronization complete");
161 }
162 }
163
164 @Override
165 public void removeListener(RouteListener listener) {
166 synchronized (this) {
167 ListenerQueue l = listeners.remove(listener);
168 if (l != null) {
169 l.stop();
170 }
171 }
172 }
173
174 /**
175 * Posts an event to all listeners.
176 *
177 * @param event event
178 */
179 private void post(RouteEvent event) {
Jonathan Hartfd176612016-04-11 10:42:10 -0700180 log.debug("Sending event {}", event);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700181 synchronized (this) {
182 listeners.values().forEach(l -> l.post(event));
183 }
184 }
185
186 @Override
187 public Map<RouteTableId, Collection<Route>> getAllRoutes() {
188 return routeStore.getRouteTables().stream()
189 .collect(Collectors.toMap(Function.identity(),
190 table -> (table == null) ?
191 Collections.emptySet() : routeStore.getRoutes(table)));
192 }
193
194 @Override
195 public Route longestPrefixMatch(IpAddress ip) {
196 return routeStore.longestPrefixMatch(ip);
197 }
198
199 @Override
Jonathan Hartfd176612016-04-11 10:42:10 -0700200 public Collection<Route> getRoutesForNextHop(IpAddress nextHop) {
201 return routeStore.getRoutesForNextHop(nextHop);
202 }
203
204 @Override
205 public Set<NextHop> getNextHops() {
206 return routeStore.getNextHops().entrySet().stream()
Charles Chanc78a0982016-11-09 16:52:11 -0800207 .map(entry -> new NextHop(entry.getKey(), entry.getValue()))
Jonathan Hartfd176612016-04-11 10:42:10 -0700208 .collect(Collectors.toSet());
209 }
210
211 @Override
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700212 public void update(Collection<Route> routes) {
213 synchronized (this) {
214 routes.forEach(route -> {
Jonathan Hartfd176612016-04-11 10:42:10 -0700215 log.debug("Received update {}", route);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700216 routeStore.updateRoute(route);
217 resolve(route);
218 });
219 }
220 }
221
222 @Override
223 public void withdraw(Collection<Route> routes) {
224 synchronized (this) {
Jonathan Hartfd176612016-04-11 10:42:10 -0700225 routes.forEach(route -> {
Charles Chanb21d69a2016-11-11 17:46:14 -0800226 log.debug("Received withdraw {}", route);
Jonathan Hartfd176612016-04-11 10:42:10 -0700227 routeStore.removeRoute(route);
228 });
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700229 }
230 }
231
232 private void resolve(Route route) {
233 // Monitor the IP address for updates of the MAC address
234 hostService.startMonitoringIp(route.nextHop());
235
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700236 NextHopData nextHopData = routeStore.getNextHop(route.nextHop());
237 if (nextHopData == null) {
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700238 Set<Host> hosts = hostService.getHostsByIp(route.nextHop());
239 Optional<Host> host = hosts.stream().findFirst();
240 if (host.isPresent()) {
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700241 nextHopData = NextHopData.fromHost(host.get());
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700242 }
243 }
244
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700245 if (nextHopData != null) {
246 routeStore.updateNextHop(route.nextHop(), nextHopData);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700247 }
248 }
249
250 private void hostUpdated(Host host) {
251 synchronized (this) {
252 for (IpAddress ip : host.ipAddresses()) {
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700253 routeStore.updateNextHop(ip, NextHopData.fromHost(host));
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700254 }
255 }
256 }
257
258 private void hostRemoved(Host host) {
259 synchronized (this) {
260 for (IpAddress ip : host.ipAddresses()) {
Charles Chan8fe9f4c2016-10-24 16:46:25 -0700261 routeStore.removeNextHop(ip, NextHopData.fromHost(host));
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700262 }
263 }
264 }
265
266 /**
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700267 * Creates a new listener queue.
268 *
269 * @param listener route listener
270 * @return listener queue
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700271 */
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700272 ListenerQueue createListenerQueue(RouteListener listener) {
273 return new DefaultListenerQueue(listener);
274 }
275
276 /**
277 * Default route listener queue.
278 */
279 private class DefaultListenerQueue implements ListenerQueue {
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700280
281 private final ExecutorService executorService;
282 private final BlockingQueue<RouteEvent> queue;
283 private final RouteListener listener;
284
285 /**
286 * Creates a new listener queue.
287 *
288 * @param listener route listener to queue updates for
289 */
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700290 public DefaultListenerQueue(RouteListener listener) {
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700291 this.listener = listener;
292 queue = new LinkedBlockingQueue<>();
293 executorService = newSingleThreadExecutor(threadFactory);
294 }
295
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700296 @Override
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700297 public void post(RouteEvent event) {
298 queue.add(event);
299 }
300
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700301 @Override
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700302 public void start() {
303 executorService.execute(this::poll);
304 }
305
Jonathan Hart6c2e7962016-04-11 13:54:09 -0700306 @Override
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700307 public void stop() {
308 executorService.shutdown();
309 }
310
311 private void poll() {
Jonathan Hartf79ab482016-08-19 14:20:50 -0700312 while (true) {
313 try {
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700314 listener.event(queue.take());
Jonathan Hartf79ab482016-08-19 14:20:50 -0700315 } catch (InterruptedException e) {
316 log.info("Route listener event thread shutting down: {}", e.getMessage());
317 break;
318 } catch (Exception e) {
319 log.warn("Exception during route event handler", e);
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700320 }
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700321 }
322 }
323
324 }
325
326 /**
327 * Delegate to receive events from the route store.
328 */
329 private class InternalRouteStoreDelegate implements RouteStoreDelegate {
330 @Override
331 public void notify(RouteEvent event) {
332 post(event);
333 }
334 }
335
336 /**
337 * Internal listener for host events.
338 */
339 private class InternalHostListener implements HostListener {
340 @Override
341 public void event(HostEvent event) {
342 switch (event.type()) {
343 case HOST_ADDED:
344 case HOST_UPDATED:
345 hostUpdated(event.subject());
346 break;
347 case HOST_REMOVED:
348 hostRemoved(event.subject());
349 break;
350 case HOST_MOVED:
351 break;
352 default:
353 break;
354 }
355 }
356 }
357
Charles Chancab494d2016-11-11 16:31:49 -0800358 private class InternalNetworkConfigListener implements NetworkConfigListener {
359 @Override
360 public void event(NetworkConfigEvent event) {
361 if (event.configClass().equals(RouteConfig.class)) {
362 switch (event.type()) {
363 case CONFIG_ADDED:
364 processRouteConfigAdded(event);
365 break;
366 case CONFIG_UPDATED:
367 processRouteConfigUpdated(event);
368 break;
369 case CONFIG_REMOVED:
370 processRouteConfigRemoved(event);
371 break;
372 default:
373 break;
374 }
375 }
376 }
377
378 private void processRouteConfigAdded(NetworkConfigEvent event) {
379 log.info("processRouteConfigAdded {}", event);
380 Set<Route> routes = ((RouteConfig) event.config().get()).getRoutes();
381 update(routes);
382 }
383
384 private void processRouteConfigUpdated(NetworkConfigEvent event) {
385 log.info("processRouteConfigUpdated {}", event);
386 Set<Route> routes = ((RouteConfig) event.config().get()).getRoutes();
387 Set<Route> prevRoutes = ((RouteConfig) event.prevConfig().get()).getRoutes();
388 Set<Route> pendingRemove = prevRoutes.stream()
389 .filter(prevRoute -> routes.stream()
390 .noneMatch(route -> route.prefix().equals(prevRoute.prefix())))
391 .collect(Collectors.toSet());
392 Set<Route> pendingUpdate = routes.stream()
393 .filter(route -> !pendingRemove.contains(route)).collect(Collectors.toSet());
394 update(pendingUpdate);
395 withdraw(pendingRemove);
396 }
397
398 private void processRouteConfigRemoved(NetworkConfigEvent event) {
399 log.info("processRouteConfigRemoved {}", event);
400 Set<Route> prevRoutes = ((RouteConfig) event.prevConfig().get()).getRoutes();
401 withdraw(prevRoutes);
402 }
403 }
Jonathan Hartbfc5c482016-04-05 18:57:00 -0700404}