| /* |
| * Copyright 2015-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.onosproject.mcast.impl; |
| |
| import com.google.common.base.Objects; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import org.apache.felix.scr.annotations.Activate; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Deactivate; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.felix.scr.annotations.ReferenceCardinality; |
| import org.apache.felix.scr.annotations.Service; |
| import org.onlab.packet.IpAddress; |
| import org.onosproject.event.AbstractListenerManager; |
| import org.onosproject.mcast.api.McastEvent; |
| import org.onosproject.mcast.api.McastListener; |
| import org.onosproject.mcast.api.McastRoute; |
| import org.onosproject.mcast.api.McastRouteData; |
| import org.onosproject.mcast.api.McastStore; |
| import org.onosproject.mcast.api.McastStoreDelegate; |
| import org.onosproject.mcast.api.MulticastRouteService; |
| import org.onosproject.net.ConnectPoint; |
| import org.onosproject.net.Host; |
| import org.onosproject.net.HostId; |
| import org.onosproject.net.HostLocation; |
| import org.onosproject.net.host.HostEvent; |
| import org.onosproject.net.host.HostListener; |
| import org.onosproject.net.host.HostService; |
| import org.slf4j.Logger; |
| |
| import java.util.HashSet; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * An implementation of a multicast route table. |
| */ |
| @Component(immediate = true) |
| @Service |
| public class MulticastRouteManager |
| extends AbstractListenerManager<McastEvent, McastListener> |
| implements MulticastRouteService { |
| //TODO: add MulticastRouteAdminService |
| |
| private Logger log = getLogger(getClass()); |
| |
| private final McastStoreDelegate delegate = new InternalMcastStoreDelegate(); |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected McastStore store; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected HostService hostService; |
| |
| private HostListener hostListener = new InternalHostListener(); |
| |
| @Activate |
| public void activate() { |
| hostService.addListener(hostListener); |
| eventDispatcher.addSink(McastEvent.class, listenerRegistry); |
| store.setDelegate(delegate); |
| log.info("Started"); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| hostService.removeListener(hostListener); |
| store.unsetDelegate(delegate); |
| eventDispatcher.removeSink(McastEvent.class); |
| log.info("Stopped"); |
| } |
| |
| @Override |
| public void add(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| store.storeRoute(route); |
| } |
| |
| @Override |
| public void remove(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| if (checkRoute(route)) { |
| store.removeRoute(route); |
| } |
| } |
| |
| @Override |
| public Set<McastRoute> getRoutes() { |
| return store.getRoutes(); |
| } |
| |
| @Override |
| public Set<McastRoute> getRoute(IpAddress groupIp, IpAddress sourceIp) { |
| // Let's transform it into an optional |
| final Optional<IpAddress> source = Optional.ofNullable(sourceIp); |
| return store.getRoutes().stream() |
| .filter(route -> route.group().equals(groupIp) && |
| Objects.equal(route.source(), source)) |
| .collect(Collectors.toSet()); |
| } |
| |
| @Override |
| public void addSource(McastRoute route, HostId source) { |
| checkNotNull(route, "Route cannot be null"); |
| checkNotNull(source, "Source cannot be null"); |
| if (checkRoute(route)) { |
| Set<ConnectPoint> sources = new HashSet<>(); |
| Host host = hostService.getHost(source); |
| if (host != null) { |
| sources.addAll(host.locations()); |
| } |
| store.storeSource(route, source, sources); |
| } |
| } |
| |
| @Override |
| public void addSources(McastRoute route, HostId hostId, Set<ConnectPoint> connectPoints) { |
| checkNotNull(route, "Route cannot be null"); |
| checkNotNull(hostId, "HostId cannot be null"); |
| checkNotNull(connectPoints, "Sources cannot be null"); |
| if (checkRoute(route)) { |
| store.storeSource(route, hostId, connectPoints); |
| } |
| } |
| |
| @Override |
| public void addSources(McastRoute route, Set<ConnectPoint> sources) { |
| checkNotNull(route, "Route cannot be null"); |
| checkNotNull(sources, "sources cannot be null"); |
| if (checkRoute(route)) { |
| store.storeSources(route, sources); |
| } |
| } |
| |
| @Override |
| public void removeSources(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| if (checkRoute(route)) { |
| store.removeSources(route); |
| } |
| } |
| |
| @Override |
| public void removeSource(McastRoute route, HostId source) { |
| checkNotNull(route, "Route cannot be null"); |
| checkNotNull(source, "Source cannot be null"); |
| if (checkRoute(route)) { |
| store.removeSource(route, source); |
| } |
| } |
| |
| @Override |
| public void addSink(McastRoute route, HostId hostId) { |
| if (checkRoute(route)) { |
| Set<ConnectPoint> sinks = new HashSet<>(); |
| Host host = hostService.getHost(hostId); |
| if (host != null) { |
| sinks.addAll(host.locations()); |
| } |
| store.addSink(route, hostId, sinks); |
| } |
| |
| } |
| |
| @Override |
| public void addSinks(McastRoute route, HostId hostId, Set<ConnectPoint> sinks) { |
| if (checkRoute(route)) { |
| store.addSink(route, hostId, sinks); |
| } |
| |
| } |
| |
| @Override |
| public void addSinks(McastRoute route, Set<ConnectPoint> sinks) { |
| checkNotNull(route, "Route cannot be null"); |
| checkNotNull(sinks, "Sinks cannot be null"); |
| if (checkRoute(route)) { |
| store.addSinks(route, sinks); |
| } |
| } |
| |
| @Override |
| public void removeSinks(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| if (checkRoute(route)) { |
| store.removeSinks(route); |
| } |
| } |
| |
| @Override |
| public void removeSink(McastRoute route, HostId hostId) { |
| checkNotNull(route, "Route cannot be null"); |
| checkNotNull(hostId, "Host cannot be null"); |
| if (checkRoute(route)) { |
| store.removeSink(route, hostId); |
| } |
| } |
| |
| @Override |
| public void removeSinks(McastRoute route, Set<ConnectPoint> connectPoints) { |
| checkNotNull(route, "Route cannot be null"); |
| if (checkRoute(route)) { |
| store.removeSinks(route, connectPoints); |
| } |
| } |
| |
| @Override |
| public McastRouteData routeData(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| return checkRoute(route) ? store.getRouteData(route) : null; |
| } |
| |
| @Override |
| public Set<ConnectPoint> sources(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| return checkRoute(route) ? store.sourcesFor(route) : ImmutableSet.of(); |
| } |
| |
| @Override |
| public Set<ConnectPoint> sources(McastRoute route, HostId hostId) { |
| checkNotNull(route, "Route cannot be null"); |
| return checkRoute(route) ? store.sourcesFor(route, hostId) : ImmutableSet.of(); |
| } |
| |
| @Override |
| public Set<ConnectPoint> sinks(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| return checkRoute(route) ? store.sinksFor(route) : ImmutableSet.of(); |
| } |
| |
| @Override |
| public Set<ConnectPoint> sinks(McastRoute route, HostId hostId) { |
| checkNotNull(route, "Route cannot be null"); |
| return checkRoute(route) ? store.sinksFor(route, hostId) : ImmutableSet.of(); |
| } |
| |
| @Override |
| public Set<ConnectPoint> nonHostSinks(McastRoute route) { |
| checkNotNull(route, "Route cannot be null"); |
| return checkRoute(route) ? store.sinksFor(route, HostId.NONE) : ImmutableSet.of(); |
| } |
| |
| private class InternalMcastStoreDelegate implements McastStoreDelegate { |
| @Override |
| public void notify(McastEvent event) { |
| log.debug("Notify event: {}", event); |
| post(event); |
| } |
| } |
| |
| private boolean checkRoute(McastRoute route) { |
| if (store.getRoutes().contains(route)) { |
| return true; |
| } else { |
| log.warn("Route {} is not present in the store, please add it", route); |
| } |
| return false; |
| } |
| |
| private class InternalHostListener implements HostListener { |
| |
| @Override |
| public void event(HostEvent event) { |
| HostId hostId = event.subject().id(); |
| log.debug("Host event: {}", event); |
| Set<McastRoute> routesForSource = routesForSource(hostId); |
| Set<McastRoute> routesForSink = routesForSink(hostId); |
| switch (event.type()) { |
| case HOST_ADDED: |
| //the host is added, if it already comes with some locations let's use them |
| if (!routesForSource.isEmpty()) { |
| eventAddSources(hostId, event.subject().locations(), routesForSource); |
| } |
| if (!routesForSink.isEmpty()) { |
| eventAddSinks(hostId, event.subject().locations(), routesForSink); |
| } |
| break; |
| case HOST_MOVED: |
| //both subjects must be null or the system is in an incoherent state |
| if ((event.prevSubject() != null && event.subject() != null)) { |
| //we compute the difference between old locations and new ones and remove the previous |
| Set<HostLocation> removedConnectPoint = Sets.difference(event.prevSubject().locations(), |
| event.subject().locations()).immutableCopy(); |
| if (!removedConnectPoint.isEmpty()) { |
| if (!routesForSource.isEmpty()) { |
| eventRemoveSources(hostId, removedConnectPoint, routesForSource); |
| } |
| if (!routesForSink.isEmpty()) { |
| eventRemoveSinks(hostId, removedConnectPoint, routesForSink); |
| } |
| } |
| Set<HostLocation> addedConnectPoints = Sets.difference(event.subject().locations(), |
| event.prevSubject().locations()).immutableCopy(); |
| //if the host now has some new locations we add them to the sinks set |
| if (!addedConnectPoints.isEmpty()) { |
| if (!routesForSource.isEmpty()) { |
| eventAddSources(hostId, addedConnectPoints, routesForSource); |
| } |
| if (!routesForSink.isEmpty()) { |
| eventAddSinks(hostId, addedConnectPoints, routesForSink); |
| } |
| } |
| } |
| break; |
| case HOST_REMOVED: |
| // Removing all the connect points for that specific host |
| // even if the locations are 0 we keep |
| // the host information in the route in case it shows up again |
| if (!routesForSource.isEmpty()) { |
| eventRemoveSources(hostId, event.subject().locations(), routesForSource); |
| } |
| if (!routesForSink.isEmpty()) { |
| eventRemoveSinks(hostId, event.subject().locations(), routesForSink); |
| } |
| break; |
| case HOST_UPDATED: |
| default: |
| log.debug("Host event {} not handled", event.type()); |
| } |
| } |
| } |
| |
| //Finds the route for which a host is source |
| private Set<McastRoute> routesForSource(HostId hostId) { |
| // Filter by host id |
| return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute) |
| .sources().containsKey(hostId)).collect(Collectors.toSet()); |
| } |
| |
| //Finds the route for which a host is sink |
| private Set<McastRoute> routesForSink(HostId hostId) { |
| return store.getRoutes().stream().filter(mcastRoute -> store.getRouteData(mcastRoute) |
| .sinks().containsKey(hostId)).collect(Collectors.toSet()); |
| } |
| |
| //Removes sources for a given host event |
| private void eventRemoveSources(HostId hostId, Set<HostLocation> removedSources, Set<McastRoute> routesForSource) { |
| Set<ConnectPoint> sources = new HashSet<>(); |
| // Build sink using host location |
| sources.addAll(removedSources); |
| // Remove from each route the provided sinks |
| routesForSource.forEach(route -> store.removeSources(route, hostId, sources)); |
| } |
| |
| //Adds the sources for a given host event |
| private void eventAddSources(HostId hostId, Set<HostLocation> addedSources, Set<McastRoute> routesForSource) { |
| Set<ConnectPoint> sources = new HashSet<>(); |
| // Build source using host location |
| sources.addAll(addedSources); |
| // Add to each route the provided sources |
| routesForSource.forEach(route -> store.storeSource(route, hostId, sources)); |
| } |
| |
| //Remove sinks for a given host event |
| private void eventRemoveSinks(HostId hostId, Set<HostLocation> removedSinks, Set<McastRoute> routesForSinks) { |
| Set<ConnectPoint> sinks = new HashSet<>(); |
| // Build sink using host location |
| sinks.addAll(removedSinks); |
| // Remove from each route the provided sinks |
| routesForSinks.forEach(route -> store.removeSinks(route, hostId, sinks)); |
| } |
| |
| //Adds the sinks for a given host event |
| private void eventAddSinks(HostId hostId, Set<HostLocation> addedSinks, Set<McastRoute> routesForSinks) { |
| Set<ConnectPoint> sinks = new HashSet<>(); |
| // Build sink using host location |
| sinks.addAll(addedSinks); |
| // Add to each route the provided sinks |
| routesForSinks.forEach(route -> store.addSink(route, hostId, sinks)); |
| } |
| } |