blob: 21870ee569f874a5c593f98910f50804cb8dac47 [file] [log] [blame]
Charles Chandebfea32016-10-24 14:52:01 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chandebfea32016-10-24 14:52:01 -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.segmentrouting;
18
Charles Chan910be6a2017-08-23 14:46:43 -070019import com.google.common.cache.Cache;
20import com.google.common.cache.CacheBuilder;
21import com.google.common.cache.RemovalCause;
22import com.google.common.cache.RemovalNotification;
Saurav Das261c3002017-06-13 15:35:54 -070023import com.google.common.collect.Sets;
24
Charles Chandebfea32016-10-24 14:52:01 -070025import org.onlab.packet.IpPrefix;
26import org.onlab.packet.MacAddress;
Charles Chan90772a72017-02-08 15:52:08 -080027import org.onlab.packet.VlanId;
Charles Chanf0ae41e2017-08-23 13:55:39 -070028import org.onosproject.net.ConnectPoint;
Charles Chan910be6a2017-08-23 14:46:43 -070029import org.onosproject.net.HostLocation;
30import org.onosproject.net.PortNumber;
31import org.onosproject.net.host.HostEvent;
Ray Milkeya8154312017-08-08 13:00:43 -070032import org.onosproject.routeservice.ResolvedRoute;
33import org.onosproject.routeservice.RouteEvent;
Charles Chandebfea32016-10-24 14:52:01 -070034import org.onosproject.net.DeviceId;
Charles Chan910be6a2017-08-23 14:46:43 -070035import org.onosproject.routeservice.RouteInfo;
Charles Chandebfea32016-10-24 14:52:01 -070036import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
Charles Chan910be6a2017-08-23 14:46:43 -070039import java.util.Collection;
Charles Chanee8dbf82017-06-22 14:15:05 -070040import java.util.Objects;
Charles Chan910be6a2017-08-23 14:46:43 -070041import java.util.Optional;
42import java.util.Set;
43import java.util.concurrent.Executors;
44import java.util.concurrent.TimeUnit;
45import java.util.stream.Collectors;
Charles Chanee8dbf82017-06-22 14:15:05 -070046
Charles Chandebfea32016-10-24 14:52:01 -070047/**
48 * Handles RouteEvent and manages routing entries.
49 */
50public class RouteHandler {
51 private static final Logger log = LoggerFactory.getLogger(RouteHandler.class);
52 private final SegmentRoutingManager srManager;
53
Charles Chan910be6a2017-08-23 14:46:43 -070054 private static final int WAIT_TIME_MS = 1000;
55 /**
56 * The routeEventCache is implemented to avoid race condition by giving more time to the
57 * underlying flow subsystem to process previous populateSubnet call.
58 */
59 private Cache<IpPrefix, RouteEvent> routeEventCache = CacheBuilder.newBuilder()
60 .expireAfterWrite(WAIT_TIME_MS, TimeUnit.MILLISECONDS)
61 .removalListener((RemovalNotification<IpPrefix, RouteEvent> notification) -> {
62 IpPrefix prefix = notification.getKey();
63 RouteEvent routeEvent = notification.getValue();
64 RemovalCause cause = notification.getCause();
65 log.debug("routeEventCache removal event. prefix={}, routeEvent={}, cause={}",
66 prefix, routeEvent, cause);
67
68 switch (notification.getCause()) {
69 case REPLACED:
70 case EXPIRED:
71 dequeueRouteEvent(routeEvent);
72 break;
73 default:
74 break;
75 }
76 }).build();
77
Charles Chanf0ae41e2017-08-23 13:55:39 -070078 RouteHandler(SegmentRoutingManager srManager) {
Charles Chandebfea32016-10-24 14:52:01 -070079 this.srManager = srManager;
Charles Chan910be6a2017-08-23 14:46:43 -070080
81 Executors.newSingleThreadScheduledExecutor()
82 .scheduleAtFixedRate(routeEventCache::cleanUp, 0, WAIT_TIME_MS, TimeUnit.MILLISECONDS);
Charles Chandebfea32016-10-24 14:52:01 -070083 }
84
85 protected void init(DeviceId deviceId) {
Charles Chanf0ae41e2017-08-23 13:55:39 -070086 srManager.routeService.getRouteTables().forEach(routeTableId ->
87 srManager.routeService.getRoutes(routeTableId).forEach(routeInfo ->
88 routeInfo.allRoutes().forEach(resolvedRoute ->
89 srManager.nextHopLocations(resolvedRoute).stream()
90 .filter(location -> deviceId.equals(location.deviceId()))
91 .forEach(location -> processRouteAddedInternal(resolvedRoute)
92 )
93 )
94 )
95 );
Charles Chandebfea32016-10-24 14:52:01 -070096 }
97
Charles Chanf0ae41e2017-08-23 13:55:39 -070098 void processRouteAdded(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -070099 enqueueRouteEvent(event);
Charles Chandebfea32016-10-24 14:52:01 -0700100 }
101
102 private void processRouteAddedInternal(ResolvedRoute route) {
Charles Chan910be6a2017-08-23 14:46:43 -0700103 processRouteAddedInternal(Sets.newHashSet(route));
104 }
105
106 private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -0700107 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -0700108 log.info("System is not ready. Skip adding route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -0700109 return;
110 }
111
Charles Chan910be6a2017-08-23 14:46:43 -0700112 log.info("processRouteAddedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -0700113
Charles Chan910be6a2017-08-23 14:46:43 -0700114 Set<ConnectPoint> allLocations = Sets.newHashSet();
115 Set<IpPrefix> allPrefixes = Sets.newHashSet();
116 routes.forEach(route -> {
117 allLocations.addAll(srManager.nextHopLocations(route));
118 allPrefixes.add(route.prefix());
119 });
120 log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
121 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700122
Charles Chan910be6a2017-08-23 14:46:43 -0700123 routes.forEach(route -> {
124 IpPrefix prefix = route.prefix();
125 MacAddress nextHopMac = route.nextHopMac();
126 VlanId nextHopVlan = route.nextHopVlan();
127 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
128
129 locations.forEach(location -> {
130 log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
131 srManager.deviceConfiguration.addSubnet(location, prefix);
132 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
133 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
134 nextHopMac, nextHopVlan, location.port());
135 });
136 });
Charles Chandebfea32016-10-24 14:52:01 -0700137 }
138
Charles Chanf0ae41e2017-08-23 13:55:39 -0700139 void processRouteUpdated(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700140 enqueueRouteEvent(event);
141 }
142
143 void processAlternativeRoutesChanged(RouteEvent event) {
144 enqueueRouteEvent(event);
145 }
146
147 private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
148 if (!isReady()) {
149 log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
150 return;
151 }
152
153 log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
154
155 Set<ConnectPoint> allLocations = Sets.newHashSet();
156 Set<IpPrefix> allPrefixes = Sets.newHashSet();
157 routes.forEach(route -> {
158 allLocations.addAll(srManager.nextHopLocations(route));
159 allPrefixes.add(route.prefix());
160 });
161 log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
162 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
163
Charles Chan910be6a2017-08-23 14:46:43 -0700164 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
165 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
166
167 toBeRemoved.forEach(route -> {
168 srManager.nextHopLocations(route).forEach(oldLocation -> {
Charles Chan06f626c2018-02-05 17:20:05 -0800169 if (toBeAdded.stream().map(srManager::nextHopLocations)
170 .flatMap(Set::stream).map(ConnectPoint::deviceId)
171 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
172 IpPrefix prefix = route.prefix();
173 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
174 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
175 // We don't remove the flow on the old location in occasion of two next hops becoming one
176 // since the populateSubnet will point the old location to the new location via spine.
177 }
Charles Chan910be6a2017-08-23 14:46:43 -0700178 });
179 });
180
181 toBeAdded.forEach(route -> {
182 IpPrefix prefix = route.prefix();
183 MacAddress nextHopMac = route.nextHopMac();
184 VlanId nextHopVlan = route.nextHopVlan();
185 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
186
187 locations.forEach(location -> {
188 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
189 srManager.deviceConfiguration.addSubnet(location, prefix);
190 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
191 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
192 nextHopMac, nextHopVlan, location.port());
193 });
194 });
Charles Chandebfea32016-10-24 14:52:01 -0700195 }
196
Charles Chanf0ae41e2017-08-23 13:55:39 -0700197 void processRouteRemoved(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700198 enqueueRouteEvent(event);
Charles Chandebfea32016-10-24 14:52:01 -0700199 }
200
Charles Chan910be6a2017-08-23 14:46:43 -0700201 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -0700202 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -0700203 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -0700204 return;
205 }
206
Charles Chan910be6a2017-08-23 14:46:43 -0700207 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -0700208
Charles Chan910be6a2017-08-23 14:46:43 -0700209 Set<IpPrefix> allPrefixes = Sets.newHashSet();
210 routes.forEach(route -> {
211 allPrefixes.add(route.prefix());
212 });
213 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
214 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700215
Charles Chan910be6a2017-08-23 14:46:43 -0700216 routes.forEach(route -> {
217 IpPrefix prefix = route.prefix();
218 MacAddress nextHopMac = route.nextHopMac();
219 VlanId nextHopVlan = route.nextHopVlan();
220 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
221
222 locations.forEach(location -> {
223 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
224 srManager.deviceConfiguration.removeSubnet(location, prefix);
225 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
226 srManager.defaultRoutingHandler.revokeRoute(location.deviceId(), prefix,
227 nextHopMac, nextHopVlan, location.port());
228
229 // Also remove redirection flows on the pair device if exists.
230 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
231 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
232 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
233 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
234 // when the host is untagged
235 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
236
237 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
238 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
239 nextHopMac, vlanId, pairLocalPort.get());
240 }
241 });
242 });
243 }
244
245 void processHostMovedEvent(HostEvent event) {
246 log.info("processHostMovedEvent {}", event);
247 MacAddress hostMac = event.subject().mac();
248 VlanId hostVlanId = event.subject().vlan();
249
250 affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
251 IpPrefix prefix = affectedRoute.prefix();
252 Set<HostLocation> prevLocations = event.prevSubject().locations();
253 Set<HostLocation> newLocations = event.subject().locations();
254
255 // For each old location
256 Sets.difference(prevLocations, newLocations).stream().filter(srManager::isMasterOf)
257 .forEach(prevLocation -> {
258 // Redirect the flows to pair link if configured
259 // Note: Do not continue removing any rule
260 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
261 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
262 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
263 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
264 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
265 // when the host is untagged
266 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
267 log.debug("HostMoved. populateRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, vlanId);
268 srManager.defaultRoutingHandler.populateRoute(prevLocation.deviceId(), prefix,
269 hostMac, vlanId, pairLocalPort.get());
270 return;
271 }
272
273 // No pair information supplied. Remove route
274 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
275 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
276 hostMac, hostVlanId, prevLocation.port());
277 });
278
279 // For each new location, add all new IPs.
280 Sets.difference(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
281 .forEach(newLocation -> {
282 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
283 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
284 hostMac, hostVlanId, newLocation.port());
285 });
286
287 });
288 }
289
290 private Set<ResolvedRoute> affectedRoutes(MacAddress mac, VlanId vlanId) {
291 return srManager.routeService.getRouteTables().stream()
292 .map(routeTableId -> srManager.routeService.getRoutes(routeTableId))
293 .flatMap(Collection::stream)
294 .map(RouteInfo::allRoutes)
295 .flatMap(Collection::stream)
296 .filter(resolvedRoute -> mac.equals(resolvedRoute.nextHopMac()) &&
297 vlanId.equals(resolvedRoute.nextHopVlan())).collect(Collectors.toSet());
Charles Chandebfea32016-10-24 14:52:01 -0700298 }
Charles Chanee8dbf82017-06-22 14:15:05 -0700299
300 private boolean isReady() {
301 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan910be6a2017-08-23 14:46:43 -0700302 Objects.nonNull(srManager.defaultRoutingHandler);
303 }
304
305 void enqueueRouteEvent(RouteEvent routeEvent) {
306 log.debug("Enqueue routeEvent {}", routeEvent);
307 routeEventCache.put(routeEvent.subject().prefix(), routeEvent);
308 }
309
310 void dequeueRouteEvent(RouteEvent routeEvent) {
311 log.debug("Dequeue routeEvent {}", routeEvent);
312 switch (routeEvent.type()) {
313 case ROUTE_ADDED:
314 processRouteAddedInternal(routeEvent.alternatives());
315 break;
316 case ROUTE_REMOVED:
317 processRouteRemovedInternal(routeEvent.alternatives());
318 break;
319 case ROUTE_UPDATED:
320 case ALTERNATIVE_ROUTES_CHANGED:
321 processRouteUpdatedInternal(Sets.newHashSet(routeEvent.alternatives()),
322 Sets.newHashSet(routeEvent.prevAlternatives()));
323 break;
324 default:
325 break;
326 }
Charles Chanee8dbf82017-06-22 14:15:05 -0700327 }
Charles Chandebfea32016-10-24 14:52:01 -0700328}