blob: a004c8dcf02e3f5c936f5fd741ade6c6362c4a91 [file] [log] [blame]
Andrea Campanella545edb42018-03-20 16:37:29 -07001/*
2 * Copyright 2015-present Open Networking Foundation
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 */
16package org.onosproject.mcast.impl;
17
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -070018import com.google.common.base.Objects;
Andrea Campanella545edb42018-03-20 16:37:29 -070019import com.google.common.collect.ImmutableSet;
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -070020import com.google.common.collect.Sets;
Andrea Campanella545edb42018-03-20 16:37:29 -070021import org.onlab.packet.IpAddress;
22import org.onosproject.event.AbstractListenerManager;
23import org.onosproject.mcast.api.McastEvent;
24import org.onosproject.mcast.api.McastListener;
25import org.onosproject.mcast.api.McastRoute;
26import org.onosproject.mcast.api.McastRouteData;
27import org.onosproject.mcast.api.McastStore;
28import org.onosproject.mcast.api.McastStoreDelegate;
29import org.onosproject.mcast.api.MulticastRouteService;
30import org.onosproject.net.ConnectPoint;
31import org.onosproject.net.Host;
32import org.onosproject.net.HostId;
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -070033import org.onosproject.net.HostLocation;
Andrea Campanella545edb42018-03-20 16:37:29 -070034import org.onosproject.net.host.HostEvent;
35import org.onosproject.net.host.HostListener;
36import org.onosproject.net.host.HostService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070037import org.osgi.service.component.annotations.Activate;
38import org.osgi.service.component.annotations.Component;
39import org.osgi.service.component.annotations.Deactivate;
40import org.osgi.service.component.annotations.Reference;
41import org.osgi.service.component.annotations.ReferenceCardinality;
Andrea Campanella545edb42018-03-20 16:37:29 -070042import org.slf4j.Logger;
43
44import java.util.HashSet;
45import java.util.Optional;
46import java.util.Set;
Jordan Halterman9aff4ea2018-08-13 02:34:35 -070047import java.util.concurrent.ExecutorService;
48import java.util.concurrent.Executors;
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -070049import java.util.stream.Collectors;
Andrea Campanella545edb42018-03-20 16:37:29 -070050
51import static com.google.common.base.Preconditions.checkNotNull;
Jordan Halterman9aff4ea2018-08-13 02:34:35 -070052import static org.onlab.util.Tools.groupedThreads;
Andrea Campanella545edb42018-03-20 16:37:29 -070053import static org.slf4j.LoggerFactory.getLogger;
54
55/**
56 * An implementation of a multicast route table.
57 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070058@Component(immediate = true, service = MulticastRouteService.class)
Andrea Campanella545edb42018-03-20 16:37:29 -070059public class MulticastRouteManager
60 extends AbstractListenerManager<McastEvent, McastListener>
61 implements MulticastRouteService {
62 //TODO: add MulticastRouteAdminService
63
64 private Logger log = getLogger(getClass());
65
66 private final McastStoreDelegate delegate = new InternalMcastStoreDelegate();
67
Ray Milkeyd84f89b2018-08-17 14:54:17 -070068 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella545edb42018-03-20 16:37:29 -070069 protected McastStore store;
70
Ray Milkeyd84f89b2018-08-17 14:54:17 -070071 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella545edb42018-03-20 16:37:29 -070072 protected HostService hostService;
73
74 private HostListener hostListener = new InternalHostListener();
Jordan Halterman9aff4ea2018-08-13 02:34:35 -070075 private ExecutorService hostEventExecutor;
Andrea Campanella545edb42018-03-20 16:37:29 -070076
77 @Activate
78 public void activate() {
Jordan Halterman9aff4ea2018-08-13 02:34:35 -070079 hostEventExecutor = Executors.newSingleThreadExecutor(groupedThreads("mcast-event-host", "%d", log));
Andrea Campanella545edb42018-03-20 16:37:29 -070080 hostService.addListener(hostListener);
81 eventDispatcher.addSink(McastEvent.class, listenerRegistry);
82 store.setDelegate(delegate);
83 log.info("Started");
84 }
85
86 @Deactivate
87 public void deactivate() {
Jordan Halterman9aff4ea2018-08-13 02:34:35 -070088 hostEventExecutor.shutdown();
Andrea Campanella545edb42018-03-20 16:37:29 -070089 hostService.removeListener(hostListener);
90 store.unsetDelegate(delegate);
91 eventDispatcher.removeSink(McastEvent.class);
92 log.info("Stopped");
93 }
94
95 @Override
96 public void add(McastRoute route) {
97 checkNotNull(route, "Route cannot be null");
98 store.storeRoute(route);
99 }
100
101 @Override
102 public void remove(McastRoute route) {
103 checkNotNull(route, "Route cannot be null");
104 if (checkRoute(route)) {
105 store.removeRoute(route);
106 }
107 }
108
109 @Override
110 public Set<McastRoute> getRoutes() {
111 return store.getRoutes();
112 }
113
114 @Override
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700115 public Set<McastRoute> getRoute(IpAddress groupIp, IpAddress sourceIp) {
116 // Let's transform it into an optional
117 final Optional<IpAddress> source = Optional.ofNullable(sourceIp);
118 return store.getRoutes().stream()
119 .filter(route -> route.group().equals(groupIp) &&
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200120 Objects.equal(route.source(), source))
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700121 .collect(Collectors.toSet());
Andrea Campanella545edb42018-03-20 16:37:29 -0700122 }
123
124 @Override
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200125 public void addSource(McastRoute route, HostId source) {
Andrea Campanella545edb42018-03-20 16:37:29 -0700126 checkNotNull(route, "Route cannot be null");
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200127 checkNotNull(source, "Source cannot be null");
Andrea Campanella545edb42018-03-20 16:37:29 -0700128 if (checkRoute(route)) {
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200129 Set<ConnectPoint> sources = new HashSet<>();
130 Host host = hostService.getHost(source);
131 if (host != null) {
132 sources.addAll(host.locations());
133 }
134 store.storeSource(route, source, sources);
135 }
136 }
137
138 @Override
139 public void addSources(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints) {
140 checkNotNull(route, "Route cannot be null");
141 checkNotNull(hostId, "HostId cannot be null");
142 checkNotNull(connectPoints, "Sources cannot be null");
143 if (checkRoute(route)) {
144 store.storeSource(route, hostId, connectPoints);
145 }
146 }
147
148 @Override
149 public void addSources(McastRoute route, Set<ConnectPoint> sources) {
150 checkNotNull(route, "Route cannot be null");
151 checkNotNull(sources, "sources cannot be null");
152 if (checkRoute(route)) {
153 store.storeSources(route, sources);
Andrea Campanella545edb42018-03-20 16:37:29 -0700154 }
155 }
156
157 @Override
158 public void removeSources(McastRoute route) {
159 checkNotNull(route, "Route cannot be null");
160 if (checkRoute(route)) {
161 store.removeSources(route);
162 }
163 }
164
165 @Override
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200166 public void removeSource(McastRoute route, HostId source) {
Andrea Campanella545edb42018-03-20 16:37:29 -0700167 checkNotNull(route, "Route cannot be null");
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200168 checkNotNull(source, "Source cannot be null");
Andrea Campanella545edb42018-03-20 16:37:29 -0700169 if (checkRoute(route)) {
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200170 store.removeSource(route, source);
Andrea Campanella545edb42018-03-20 16:37:29 -0700171 }
172 }
173
174 @Override
175 public void addSink(McastRoute route, HostId hostId) {
176 if (checkRoute(route)) {
177 Set<ConnectPoint> sinks = new HashSet<>();
178 Host host = hostService.getHost(hostId);
179 if (host != null) {
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200180 sinks.addAll(host.locations());
Andrea Campanella545edb42018-03-20 16:37:29 -0700181 }
182 store.addSink(route, hostId, sinks);
183 }
184
185 }
186
187 @Override
Andrea Campanella644a8a62018-03-21 19:08:21 -0700188 public void addSinks(McastRoute route, HostId hostId, Set<ConnectPoint> sinks) {
189 if (checkRoute(route)) {
190 store.addSink(route, hostId, sinks);
191 }
192
193 }
194
195 @Override
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200196 public void addSinks(McastRoute route, Set<ConnectPoint> sinks) {
Andrea Campanella545edb42018-03-20 16:37:29 -0700197 checkNotNull(route, "Route cannot be null");
198 checkNotNull(sinks, "Sinks cannot be null");
199 if (checkRoute(route)) {
200 store.addSinks(route, sinks);
201 }
202 }
203
204 @Override
205 public void removeSinks(McastRoute route) {
206 checkNotNull(route, "Route cannot be null");
207 if (checkRoute(route)) {
208 store.removeSinks(route);
209 }
210 }
211
212 @Override
213 public void removeSink(McastRoute route, HostId hostId) {
214 checkNotNull(route, "Route cannot be null");
215 checkNotNull(hostId, "Host cannot be null");
216 if (checkRoute(route)) {
217 store.removeSink(route, hostId);
218 }
219 }
220
221 @Override
Andrea Campanella545edb42018-03-20 16:37:29 -0700222 public void removeSinks(McastRoute route, Set<ConnectPoint> connectPoints) {
223 checkNotNull(route, "Route cannot be null");
224 if (checkRoute(route)) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700225 store.removeSinks(route, connectPoints);
Andrea Campanella545edb42018-03-20 16:37:29 -0700226 }
227 }
228
229 @Override
230 public McastRouteData routeData(McastRoute route) {
231 checkNotNull(route, "Route cannot be null");
232 return checkRoute(route) ? store.getRouteData(route) : null;
233 }
234
235 @Override
236 public Set<ConnectPoint> sources(McastRoute route) {
237 checkNotNull(route, "Route cannot be null");
238 return checkRoute(route) ? store.sourcesFor(route) : ImmutableSet.of();
239 }
240
241 @Override
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200242 public Set<ConnectPoint> sources(McastRoute route, HostId hostId) {
243 checkNotNull(route, "Route cannot be null");
244 return checkRoute(route) ? store.sourcesFor(route, hostId) : ImmutableSet.of();
245 }
246
247 @Override
Andrea Campanella545edb42018-03-20 16:37:29 -0700248 public Set<ConnectPoint> sinks(McastRoute route) {
249 checkNotNull(route, "Route cannot be null");
250 return checkRoute(route) ? store.sinksFor(route) : ImmutableSet.of();
251 }
252
253 @Override
254 public Set<ConnectPoint> sinks(McastRoute route, HostId hostId) {
255 checkNotNull(route, "Route cannot be null");
256 return checkRoute(route) ? store.sinksFor(route, hostId) : ImmutableSet.of();
257 }
258
259 @Override
260 public Set<ConnectPoint> nonHostSinks(McastRoute route) {
261 checkNotNull(route, "Route cannot be null");
262 return checkRoute(route) ? store.sinksFor(route, HostId.NONE) : ImmutableSet.of();
263 }
264
265 private class InternalMcastStoreDelegate implements McastStoreDelegate {
266 @Override
267 public void notify(McastEvent event) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700268 log.debug("Notify event: {}", event);
Andrea Campanella545edb42018-03-20 16:37:29 -0700269 post(event);
270 }
271 }
272
273 private boolean checkRoute(McastRoute route) {
274 if (store.getRoutes().contains(route)) {
275 return true;
276 } else {
277 log.warn("Route {} is not present in the store, please add it", route);
278 }
279 return false;
280 }
281
282 private class InternalHostListener implements HostListener {
283
284 @Override
285 public void event(HostEvent event) {
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700286 hostEventExecutor.execute(() -> {
287 HostId hostId = event.subject().id();
288 log.debug("Host event: {}", event);
289 Set<McastRoute> routesForSource = routesForSource(hostId);
290 Set<McastRoute> routesForSink = routesForSink(hostId);
291 switch (event.type()) {
292 case HOST_ADDED:
293 //the host is added, if it already comes with some locations let's use them
294 if (!routesForSource.isEmpty()) {
295 eventAddSources(hostId, event.subject().locations(), routesForSource);
296 }
297 if (!routesForSink.isEmpty()) {
298 eventAddSinks(hostId, event.subject().locations(), routesForSink);
299 }
300 break;
301 case HOST_MOVED:
302 //both subjects must be null or the system is in an incoherent state
303 if ((event.prevSubject() != null && event.subject() != null)) {
304 //we compute the difference between old locations and new ones and remove the previous
305 Set<HostLocation> removedConnectPoint = Sets.difference(event.prevSubject().locations(),
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700306 event.subject().locations()).immutableCopy();
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700307 if (!removedConnectPoint.isEmpty()) {
308 if (!routesForSource.isEmpty()) {
309 eventRemoveSources(hostId, removedConnectPoint, routesForSource);
310 }
311 if (!routesForSink.isEmpty()) {
312 eventRemoveSinks(hostId, removedConnectPoint, routesForSink);
313 }
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200314 }
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700315 Set<HostLocation> addedConnectPoints = Sets.difference(event.subject().locations(),
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700316 event.prevSubject().locations()).immutableCopy();
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700317 //if the host now has some new locations we add them to the sinks set
318 if (!addedConnectPoints.isEmpty()) {
319 if (!routesForSource.isEmpty()) {
320 eventAddSources(hostId, addedConnectPoints, routesForSource);
321 }
322 if (!routesForSink.isEmpty()) {
323 eventAddSinks(hostId, addedConnectPoints, routesForSink);
324 }
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200325 }
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700326 }
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700327 break;
328 case HOST_REMOVED:
329 // Removing all the connect points for that specific host
330 // even if the locations are 0 we keep
331 // the host information in the route in case it shows up again
332 if (!routesForSource.isEmpty()) {
333 eventRemoveSources(hostId, event.subject().locations(), routesForSource);
334 }
335 if (!routesForSink.isEmpty()) {
336 eventRemoveSinks(hostId, event.subject().locations(), routesForSink);
337 }
338 break;
339 case HOST_UPDATED:
340 default:
341 log.debug("Host event {} not handled", event.type());
342 }
343 });
Andrea Campanella545edb42018-03-20 16:37:29 -0700344 }
345 }
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700346
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200347 //Finds the route for which a host is source
348 private Set<McastRoute> routesForSource(HostId hostId) {
349 // Filter by host id
350 return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
351 .sources().containsKey(hostId)).collect(Collectors.toSet());
352 }
353
354 //Finds the route for which a host is sink
355 private Set<McastRoute> routesForSink(HostId hostId) {
356 return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
357 .sinks().containsKey(hostId)).collect(Collectors.toSet());
358 }
359
360 //Removes sources for a given host event
361 private void eventRemoveSources(HostId hostId, Set<HostLocation> removedSources, Set<McastRoute> routesForSource) {
362 Set<ConnectPoint> sources = new HashSet<>();
363 // Build sink using host location
364 sources.addAll(removedSources);
365 // Remove from each route the provided sinks
366 routesForSource.forEach(route -> store.removeSources(route, hostId, sources));
367 }
368
369 //Adds the sources for a given host event
370 private void eventAddSources(HostId hostId, Set<HostLocation> addedSources, Set<McastRoute> routesForSource) {
371 Set<ConnectPoint> sources = new HashSet<>();
372 // Build source using host location
373 sources.addAll(addedSources);
374 // Add to each route the provided sources
375 routesForSource.forEach(route -> store.storeSource(route, hostId, sources));
376 }
377
378 //Remove sinks for a given host event
379 private void eventRemoveSinks(HostId hostId, Set<HostLocation> removedSinks, Set<McastRoute> routesForSinks) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700380 Set<ConnectPoint> sinks = new HashSet<>();
381 // Build sink using host location
382 sinks.addAll(removedSinks);
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200383 // Remove from each route the provided sinks
384 routesForSinks.forEach(route -> store.removeSinks(route, hostId, sinks));
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700385 }
386
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200387 //Adds the sinks for a given host event
388 private void eventAddSinks(HostId hostId, Set<HostLocation> addedSinks, Set<McastRoute> routesForSinks) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700389 Set<ConnectPoint> sinks = new HashSet<>();
390 // Build sink using host location
391 sinks.addAll(addedSinks);
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200392 // Add to each route the provided sinks
393 routesForSinks.forEach(route -> store.addSink(route, hostId, sinks));
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700394 }
Andrea Campanella545edb42018-03-20 16:37:29 -0700395}