blob: 51d42075fe62583a65a95dd0b06d947dfa315c75 [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
Ilayda Ozdemirad741712020-06-10 08:31:27 +0000175 public void removeSources(McastRoute route, Set<ConnectPoint> connectPoints) {
176 checkNotNull(route, "Route cannot be null");
177 checkNotNull(connectPoints, "ConnectPoints cannot be null");
178 if (checkRoute(route)) {
179 store.removeSources(route, connectPoints);
180 }
181 }
182
183 @Override
184 public void removeSources(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints) {
185 checkNotNull(route, "Route cannot be null");
186 checkNotNull(hostId, "HostId cannot be null");
187 checkNotNull(connectPoints, "ConnectPoints cannot be null");
188 if (checkRoute(route)) {
189 store.removeSources(route, hostId, connectPoints);
190 }
191 }
192
193 @Override
Andrea Campanella545edb42018-03-20 16:37:29 -0700194 public void addSink(McastRoute route, HostId hostId) {
195 if (checkRoute(route)) {
196 Set<ConnectPoint> sinks = new HashSet<>();
197 Host host = hostService.getHost(hostId);
198 if (host != null) {
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200199 sinks.addAll(host.locations());
Andrea Campanella545edb42018-03-20 16:37:29 -0700200 }
201 store.addSink(route, hostId, sinks);
202 }
203
204 }
205
206 @Override
Andrea Campanella644a8a62018-03-21 19:08:21 -0700207 public void addSinks(McastRoute route, HostId hostId, Set<ConnectPoint> sinks) {
208 if (checkRoute(route)) {
209 store.addSink(route, hostId, sinks);
210 }
211
212 }
213
214 @Override
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200215 public void addSinks(McastRoute route, Set<ConnectPoint> sinks) {
Andrea Campanella545edb42018-03-20 16:37:29 -0700216 checkNotNull(route, "Route cannot be null");
217 checkNotNull(sinks, "Sinks cannot be null");
218 if (checkRoute(route)) {
219 store.addSinks(route, sinks);
220 }
221 }
222
223 @Override
224 public void removeSinks(McastRoute route) {
225 checkNotNull(route, "Route cannot be null");
226 if (checkRoute(route)) {
227 store.removeSinks(route);
228 }
229 }
230
231 @Override
232 public void removeSink(McastRoute route, HostId hostId) {
233 checkNotNull(route, "Route cannot be null");
234 checkNotNull(hostId, "Host cannot be null");
235 if (checkRoute(route)) {
236 store.removeSink(route, hostId);
237 }
238 }
239
240 @Override
Andrea Campanella545edb42018-03-20 16:37:29 -0700241 public void removeSinks(McastRoute route, Set<ConnectPoint> connectPoints) {
242 checkNotNull(route, "Route cannot be null");
243 if (checkRoute(route)) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700244 store.removeSinks(route, connectPoints);
Andrea Campanella545edb42018-03-20 16:37:29 -0700245 }
246 }
247
248 @Override
249 public McastRouteData routeData(McastRoute route) {
250 checkNotNull(route, "Route cannot be null");
251 return checkRoute(route) ? store.getRouteData(route) : null;
252 }
253
254 @Override
255 public Set<ConnectPoint> sources(McastRoute route) {
256 checkNotNull(route, "Route cannot be null");
257 return checkRoute(route) ? store.sourcesFor(route) : ImmutableSet.of();
258 }
259
260 @Override
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200261 public Set<ConnectPoint> sources(McastRoute route, HostId hostId) {
262 checkNotNull(route, "Route cannot be null");
263 return checkRoute(route) ? store.sourcesFor(route, hostId) : ImmutableSet.of();
264 }
265
266 @Override
Andrea Campanella545edb42018-03-20 16:37:29 -0700267 public Set<ConnectPoint> sinks(McastRoute route) {
268 checkNotNull(route, "Route cannot be null");
269 return checkRoute(route) ? store.sinksFor(route) : ImmutableSet.of();
270 }
271
272 @Override
273 public Set<ConnectPoint> sinks(McastRoute route, HostId hostId) {
274 checkNotNull(route, "Route cannot be null");
275 return checkRoute(route) ? store.sinksFor(route, hostId) : ImmutableSet.of();
276 }
277
278 @Override
279 public Set<ConnectPoint> nonHostSinks(McastRoute route) {
280 checkNotNull(route, "Route cannot be null");
281 return checkRoute(route) ? store.sinksFor(route, HostId.NONE) : ImmutableSet.of();
282 }
283
284 private class InternalMcastStoreDelegate implements McastStoreDelegate {
285 @Override
286 public void notify(McastEvent event) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700287 log.debug("Notify event: {}", event);
Andrea Campanella545edb42018-03-20 16:37:29 -0700288 post(event);
289 }
290 }
291
292 private boolean checkRoute(McastRoute route) {
293 if (store.getRoutes().contains(route)) {
294 return true;
295 } else {
296 log.warn("Route {} is not present in the store, please add it", route);
297 }
298 return false;
299 }
300
301 private class InternalHostListener implements HostListener {
302
303 @Override
304 public void event(HostEvent event) {
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700305 hostEventExecutor.execute(() -> {
306 HostId hostId = event.subject().id();
307 log.debug("Host event: {}", event);
308 Set<McastRoute> routesForSource = routesForSource(hostId);
309 Set<McastRoute> routesForSink = routesForSink(hostId);
310 switch (event.type()) {
311 case HOST_ADDED:
312 //the host is added, if it already comes with some locations let's use them
313 if (!routesForSource.isEmpty()) {
314 eventAddSources(hostId, event.subject().locations(), routesForSource);
315 }
316 if (!routesForSink.isEmpty()) {
317 eventAddSinks(hostId, event.subject().locations(), routesForSink);
318 }
319 break;
320 case HOST_MOVED:
321 //both subjects must be null or the system is in an incoherent state
322 if ((event.prevSubject() != null && event.subject() != null)) {
323 //we compute the difference between old locations and new ones and remove the previous
324 Set<HostLocation> removedConnectPoint = Sets.difference(event.prevSubject().locations(),
Ilayda Ozdemirad741712020-06-10 08:31:27 +0000325 event.subject().locations()).immutableCopy();
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700326 if (!removedConnectPoint.isEmpty()) {
327 if (!routesForSource.isEmpty()) {
328 eventRemoveSources(hostId, removedConnectPoint, routesForSource);
329 }
330 if (!routesForSink.isEmpty()) {
331 eventRemoveSinks(hostId, removedConnectPoint, routesForSink);
332 }
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200333 }
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700334 Set<HostLocation> addedConnectPoints = Sets.difference(event.subject().locations(),
Ilayda Ozdemirad741712020-06-10 08:31:27 +0000335 event.prevSubject().locations()).immutableCopy();
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700336 //if the host now has some new locations we add them to the sinks set
337 if (!addedConnectPoints.isEmpty()) {
338 if (!routesForSource.isEmpty()) {
339 eventAddSources(hostId, addedConnectPoints, routesForSource);
340 }
341 if (!routesForSink.isEmpty()) {
342 eventAddSinks(hostId, addedConnectPoints, routesForSink);
343 }
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200344 }
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700345 }
Jordan Halterman9aff4ea2018-08-13 02:34:35 -0700346 break;
347 case HOST_REMOVED:
348 // Removing all the connect points for that specific host
349 // even if the locations are 0 we keep
350 // the host information in the route in case it shows up again
351 if (!routesForSource.isEmpty()) {
352 eventRemoveSources(hostId, event.subject().locations(), routesForSource);
353 }
354 if (!routesForSink.isEmpty()) {
355 eventRemoveSinks(hostId, event.subject().locations(), routesForSink);
356 }
357 break;
358 case HOST_UPDATED:
359 default:
360 log.debug("Host event {} not handled", event.type());
361 }
362 });
Andrea Campanella545edb42018-03-20 16:37:29 -0700363 }
364 }
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700365
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200366 //Finds the route for which a host is source
367 private Set<McastRoute> routesForSource(HostId hostId) {
368 // Filter by host id
369 return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
370 .sources().containsKey(hostId)).collect(Collectors.toSet());
371 }
372
373 //Finds the route for which a host is sink
374 private Set<McastRoute> routesForSink(HostId hostId) {
375 return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute)
376 .sinks().containsKey(hostId)).collect(Collectors.toSet());
377 }
378
379 //Removes sources for a given host event
380 private void eventRemoveSources(HostId hostId, Set<HostLocation> removedSources, Set<McastRoute> routesForSource) {
381 Set<ConnectPoint> sources = new HashSet<>();
382 // Build sink using host location
383 sources.addAll(removedSources);
384 // Remove from each route the provided sinks
385 routesForSource.forEach(route -> store.removeSources(route, hostId, sources));
386 }
387
388 //Adds the sources for a given host event
389 private void eventAddSources(HostId hostId, Set<HostLocation> addedSources, Set<McastRoute> routesForSource) {
390 Set<ConnectPoint> sources = new HashSet<>();
391 // Build source using host location
392 sources.addAll(addedSources);
393 // Add to each route the provided sources
394 routesForSource.forEach(route -> store.storeSource(route, hostId, sources));
395 }
396
397 //Remove sinks for a given host event
398 private void eventRemoveSinks(HostId hostId, Set<HostLocation> removedSinks, Set<McastRoute> routesForSinks) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700399 Set<ConnectPoint> sinks = new HashSet<>();
400 // Build sink using host location
401 sinks.addAll(removedSinks);
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200402 // Remove from each route the provided sinks
403 routesForSinks.forEach(route -> store.removeSinks(route, hostId, sinks));
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700404 }
405
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200406 //Adds the sinks for a given host event
407 private void eventAddSinks(HostId hostId, Set<HostLocation> addedSinks, Set<McastRoute> routesForSinks) {
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700408 Set<ConnectPoint> sinks = new HashSet<>();
409 // Build sink using host location
410 sinks.addAll(addedSinks);
Andrea Campanella0ddf9b82018-04-27 15:54:42 +0200411 // Add to each route the provided sinks
412 routesForSinks.forEach(route -> store.addSink(route, hostId, sinks));
Andrea Campanella7c8bcdf2018-03-26 23:29:11 -0700413 }
Andrea Campanella545edb42018-03-20 16:37:29 -0700414}