blob: 21870ee569f874a5c593f98910f50804cb8dac47 [file] [log] [blame]
/*
* Copyright 2016-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.segmentrouting;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.Sets;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.HostLocation;
import org.onosproject.net.PortNumber;
import org.onosproject.net.host.HostEvent;
import org.onosproject.routeservice.ResolvedRoute;
import org.onosproject.routeservice.RouteEvent;
import org.onosproject.net.DeviceId;
import org.onosproject.routeservice.RouteInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Handles RouteEvent and manages routing entries.
*/
public class RouteHandler {
private static final Logger log = LoggerFactory.getLogger(RouteHandler.class);
private final SegmentRoutingManager srManager;
private static final int WAIT_TIME_MS = 1000;
/**
* The routeEventCache is implemented to avoid race condition by giving more time to the
* underlying flow subsystem to process previous populateSubnet call.
*/
private Cache<IpPrefix, RouteEvent> routeEventCache = CacheBuilder.newBuilder()
.expireAfterWrite(WAIT_TIME_MS, TimeUnit.MILLISECONDS)
.removalListener((RemovalNotification<IpPrefix, RouteEvent> notification) -> {
IpPrefix prefix = notification.getKey();
RouteEvent routeEvent = notification.getValue();
RemovalCause cause = notification.getCause();
log.debug("routeEventCache removal event. prefix={}, routeEvent={}, cause={}",
prefix, routeEvent, cause);
switch (notification.getCause()) {
case REPLACED:
case EXPIRED:
dequeueRouteEvent(routeEvent);
break;
default:
break;
}
}).build();
RouteHandler(SegmentRoutingManager srManager) {
this.srManager = srManager;
Executors.newSingleThreadScheduledExecutor()
.scheduleAtFixedRate(routeEventCache::cleanUp, 0, WAIT_TIME_MS, TimeUnit.MILLISECONDS);
}
protected void init(DeviceId deviceId) {
srManager.routeService.getRouteTables().forEach(routeTableId ->
srManager.routeService.getRoutes(routeTableId).forEach(routeInfo ->
routeInfo.allRoutes().forEach(resolvedRoute ->
srManager.nextHopLocations(resolvedRoute).stream()
.filter(location -> deviceId.equals(location.deviceId()))
.forEach(location -> processRouteAddedInternal(resolvedRoute)
)
)
)
);
}
void processRouteAdded(RouteEvent event) {
enqueueRouteEvent(event);
}
private void processRouteAddedInternal(ResolvedRoute route) {
processRouteAddedInternal(Sets.newHashSet(route));
}
private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
if (!isReady()) {
log.info("System is not ready. Skip adding route for {}", routes);
return;
}
log.info("processRouteAddedInternal. routes={}", routes);
Set<ConnectPoint> allLocations = Sets.newHashSet();
Set<IpPrefix> allPrefixes = Sets.newHashSet();
routes.forEach(route -> {
allLocations.addAll(srManager.nextHopLocations(route));
allPrefixes.add(route.prefix());
});
log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
routes.forEach(route -> {
IpPrefix prefix = route.prefix();
MacAddress nextHopMac = route.nextHopMac();
VlanId nextHopVlan = route.nextHopVlan();
Set<ConnectPoint> locations = srManager.nextHopLocations(route);
locations.forEach(location -> {
log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
srManager.deviceConfiguration.addSubnet(location, prefix);
log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
nextHopMac, nextHopVlan, location.port());
});
});
}
void processRouteUpdated(RouteEvent event) {
enqueueRouteEvent(event);
}
void processAlternativeRoutesChanged(RouteEvent event) {
enqueueRouteEvent(event);
}
private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
if (!isReady()) {
log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
return;
}
log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
Set<ConnectPoint> allLocations = Sets.newHashSet();
Set<IpPrefix> allPrefixes = Sets.newHashSet();
routes.forEach(route -> {
allLocations.addAll(srManager.nextHopLocations(route));
allPrefixes.add(route.prefix());
});
log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
toBeRemoved.forEach(route -> {
srManager.nextHopLocations(route).forEach(oldLocation -> {
if (toBeAdded.stream().map(srManager::nextHopLocations)
.flatMap(Set::stream).map(ConnectPoint::deviceId)
.noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
IpPrefix prefix = route.prefix();
log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
// We don't remove the flow on the old location in occasion of two next hops becoming one
// since the populateSubnet will point the old location to the new location via spine.
}
});
});
toBeAdded.forEach(route -> {
IpPrefix prefix = route.prefix();
MacAddress nextHopMac = route.nextHopMac();
VlanId nextHopVlan = route.nextHopVlan();
Set<ConnectPoint> locations = srManager.nextHopLocations(route);
locations.forEach(location -> {
log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
srManager.deviceConfiguration.addSubnet(location, prefix);
log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
nextHopMac, nextHopVlan, location.port());
});
});
}
void processRouteRemoved(RouteEvent event) {
enqueueRouteEvent(event);
}
private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
if (!isReady()) {
log.info("System is not ready. Skip removing route for {}", routes);
return;
}
log.info("processRouteRemovedInternal. routes={}", routes);
Set<IpPrefix> allPrefixes = Sets.newHashSet();
routes.forEach(route -> {
allPrefixes.add(route.prefix());
});
log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
routes.forEach(route -> {
IpPrefix prefix = route.prefix();
MacAddress nextHopMac = route.nextHopMac();
VlanId nextHopVlan = route.nextHopVlan();
Set<ConnectPoint> locations = srManager.nextHopLocations(route);
locations.forEach(location -> {
log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
srManager.deviceConfiguration.removeSubnet(location, prefix);
log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
srManager.defaultRoutingHandler.revokeRoute(location.deviceId(), prefix,
nextHopMac, nextHopVlan, location.port());
// Also remove redirection flows on the pair device if exists.
Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
// NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
// when the host is untagged
VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
nextHopMac, vlanId, pairLocalPort.get());
}
});
});
}
void processHostMovedEvent(HostEvent event) {
log.info("processHostMovedEvent {}", event);
MacAddress hostMac = event.subject().mac();
VlanId hostVlanId = event.subject().vlan();
affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
IpPrefix prefix = affectedRoute.prefix();
Set<HostLocation> prevLocations = event.prevSubject().locations();
Set<HostLocation> newLocations = event.subject().locations();
// For each old location
Sets.difference(prevLocations, newLocations).stream().filter(srManager::isMasterOf)
.forEach(prevLocation -> {
// Redirect the flows to pair link if configured
// Note: Do not continue removing any rule
Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
.anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
// NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
// when the host is untagged
VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
log.debug("HostMoved. populateRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, vlanId);
srManager.defaultRoutingHandler.populateRoute(prevLocation.deviceId(), prefix,
hostMac, vlanId, pairLocalPort.get());
return;
}
// No pair information supplied. Remove route
log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
hostMac, hostVlanId, prevLocation.port());
});
// For each new location, add all new IPs.
Sets.difference(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
.forEach(newLocation -> {
log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
hostMac, hostVlanId, newLocation.port());
});
});
}
private Set<ResolvedRoute> affectedRoutes(MacAddress mac, VlanId vlanId) {
return srManager.routeService.getRouteTables().stream()
.map(routeTableId -> srManager.routeService.getRoutes(routeTableId))
.flatMap(Collection::stream)
.map(RouteInfo::allRoutes)
.flatMap(Collection::stream)
.filter(resolvedRoute -> mac.equals(resolvedRoute.nextHopMac()) &&
vlanId.equals(resolvedRoute.nextHopVlan())).collect(Collectors.toSet());
}
private boolean isReady() {
return Objects.nonNull(srManager.deviceConfiguration) &&
Objects.nonNull(srManager.defaultRoutingHandler);
}
void enqueueRouteEvent(RouteEvent routeEvent) {
log.debug("Enqueue routeEvent {}", routeEvent);
routeEventCache.put(routeEvent.subject().prefix(), routeEvent);
}
void dequeueRouteEvent(RouteEvent routeEvent) {
log.debug("Dequeue routeEvent {}", routeEvent);
switch (routeEvent.type()) {
case ROUTE_ADDED:
processRouteAddedInternal(routeEvent.alternatives());
break;
case ROUTE_REMOVED:
processRouteRemovedInternal(routeEvent.alternatives());
break;
case ROUTE_UPDATED:
case ALTERNATIVE_ROUTES_CHANGED:
processRouteUpdatedInternal(Sets.newHashSet(routeEvent.alternatives()),
Sets.newHashSet(routeEvent.prevAlternatives()));
break;
default:
break;
}
}
}