blob: 6d77da414bf672bd88cc19b00051b4537108d974 [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 Chan482b6422018-04-09 11:52:08 -0400114 if (routes.size() > 2) {
115 log.info("Route {} has more than two next hops. Do not process route change", routes);
116 return;
117 }
118
Charles Chan910be6a2017-08-23 14:46:43 -0700119 Set<ConnectPoint> allLocations = Sets.newHashSet();
120 Set<IpPrefix> allPrefixes = Sets.newHashSet();
121 routes.forEach(route -> {
122 allLocations.addAll(srManager.nextHopLocations(route));
123 allPrefixes.add(route.prefix());
124 });
125 log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
126 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700127
Charles Chan910be6a2017-08-23 14:46:43 -0700128 routes.forEach(route -> {
129 IpPrefix prefix = route.prefix();
130 MacAddress nextHopMac = route.nextHopMac();
131 VlanId nextHopVlan = route.nextHopVlan();
132 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
133
134 locations.forEach(location -> {
135 log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
136 srManager.deviceConfiguration.addSubnet(location, prefix);
137 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
138 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
139 nextHopMac, nextHopVlan, location.port());
140 });
141 });
Charles Chandebfea32016-10-24 14:52:01 -0700142 }
143
Charles Chanf0ae41e2017-08-23 13:55:39 -0700144 void processRouteUpdated(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700145 enqueueRouteEvent(event);
146 }
147
148 void processAlternativeRoutesChanged(RouteEvent event) {
149 enqueueRouteEvent(event);
150 }
151
152 private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
153 if (!isReady()) {
154 log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
155 return;
156 }
157
158 log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
159
Charles Chan482b6422018-04-09 11:52:08 -0400160 if (routes.size() > 2) {
161 log.info("Route {} has more than two next hops. Do not process route change", routes);
162 return;
163 }
164
Charles Chan910be6a2017-08-23 14:46:43 -0700165 Set<ConnectPoint> allLocations = Sets.newHashSet();
166 Set<IpPrefix> allPrefixes = Sets.newHashSet();
167 routes.forEach(route -> {
168 allLocations.addAll(srManager.nextHopLocations(route));
169 allPrefixes.add(route.prefix());
170 });
Charles Chan482b6422018-04-09 11:52:08 -0400171
172 // Just come back from an invalid next hop count
173 // Revoke subnet from all locations and reset oldRoutes such that system will be reprogrammed from scratch
174 if (oldRoutes.size() > 2) {
175 log.info("Revoke subnet {} and reset oldRoutes");
176 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
177 oldRoutes = Sets.newHashSet();
178 }
179
Charles Chan910be6a2017-08-23 14:46:43 -0700180 log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
181 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
182
Charles Chan910be6a2017-08-23 14:46:43 -0700183 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
184 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
185
186 toBeRemoved.forEach(route -> {
187 srManager.nextHopLocations(route).forEach(oldLocation -> {
Charles Chan06f626c2018-02-05 17:20:05 -0800188 if (toBeAdded.stream().map(srManager::nextHopLocations)
189 .flatMap(Set::stream).map(ConnectPoint::deviceId)
190 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
191 IpPrefix prefix = route.prefix();
192 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
193 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
194 // We don't remove the flow on the old location in occasion of two next hops becoming one
195 // since the populateSubnet will point the old location to the new location via spine.
196 }
Charles Chan910be6a2017-08-23 14:46:43 -0700197 });
198 });
199
200 toBeAdded.forEach(route -> {
201 IpPrefix prefix = route.prefix();
202 MacAddress nextHopMac = route.nextHopMac();
203 VlanId nextHopVlan = route.nextHopVlan();
204 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
205
206 locations.forEach(location -> {
207 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
208 srManager.deviceConfiguration.addSubnet(location, prefix);
209 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
210 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
211 nextHopMac, nextHopVlan, location.port());
212 });
213 });
Charles Chandebfea32016-10-24 14:52:01 -0700214 }
215
Charles Chanf0ae41e2017-08-23 13:55:39 -0700216 void processRouteRemoved(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700217 enqueueRouteEvent(event);
Charles Chandebfea32016-10-24 14:52:01 -0700218 }
219
Charles Chan910be6a2017-08-23 14:46:43 -0700220 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -0700221 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -0700222 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -0700223 return;
224 }
225
Charles Chan910be6a2017-08-23 14:46:43 -0700226 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -0700227
Charles Chan910be6a2017-08-23 14:46:43 -0700228 Set<IpPrefix> allPrefixes = Sets.newHashSet();
229 routes.forEach(route -> {
230 allPrefixes.add(route.prefix());
231 });
232 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
233 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700234
Charles Chan910be6a2017-08-23 14:46:43 -0700235 routes.forEach(route -> {
236 IpPrefix prefix = route.prefix();
237 MacAddress nextHopMac = route.nextHopMac();
238 VlanId nextHopVlan = route.nextHopVlan();
239 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
240
241 locations.forEach(location -> {
242 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
243 srManager.deviceConfiguration.removeSubnet(location, prefix);
Charles Chanc4d68882018-03-15 16:41:10 -0700244 // We don't need to call revokeRoute again since revokeSubnet will remove the prefix
245 // from all devices, including the ones that next hop attaches to.
Charles Chan910be6a2017-08-23 14:46:43 -0700246
247 // Also remove redirection flows on the pair device if exists.
248 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
249 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
250 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
251 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
252 // when the host is untagged
253 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
254
255 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
256 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
257 nextHopMac, vlanId, pairLocalPort.get());
258 }
259 });
260 });
261 }
262
263 void processHostMovedEvent(HostEvent event) {
264 log.info("processHostMovedEvent {}", event);
265 MacAddress hostMac = event.subject().mac();
266 VlanId hostVlanId = event.subject().vlan();
267
268 affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
269 IpPrefix prefix = affectedRoute.prefix();
270 Set<HostLocation> prevLocations = event.prevSubject().locations();
271 Set<HostLocation> newLocations = event.subject().locations();
272
273 // For each old location
Charles Chand66d6712018-03-29 16:03:41 -0700274 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Charles Chan910be6a2017-08-23 14:46:43 -0700275 // Redirect the flows to pair link if configured
276 // Note: Do not continue removing any rule
277 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
278 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
279 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
280 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
281 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
282 // when the host is untagged
283 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
284 log.debug("HostMoved. populateRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, vlanId);
285 srManager.defaultRoutingHandler.populateRoute(prevLocation.deviceId(), prefix,
286 hostMac, vlanId, pairLocalPort.get());
287 return;
288 }
289
290 // No pair information supplied. Remove route
291 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
292 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
293 hostMac, hostVlanId, prevLocation.port());
294 });
295
296 // For each new location, add all new IPs.
Charles Chand66d6712018-03-29 16:03:41 -0700297 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
Charles Chan910be6a2017-08-23 14:46:43 -0700298 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
299 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
300 hostMac, hostVlanId, newLocation.port());
301 });
302
303 });
304 }
305
306 private Set<ResolvedRoute> affectedRoutes(MacAddress mac, VlanId vlanId) {
307 return srManager.routeService.getRouteTables().stream()
308 .map(routeTableId -> srManager.routeService.getRoutes(routeTableId))
309 .flatMap(Collection::stream)
310 .map(RouteInfo::allRoutes)
311 .flatMap(Collection::stream)
312 .filter(resolvedRoute -> mac.equals(resolvedRoute.nextHopMac()) &&
313 vlanId.equals(resolvedRoute.nextHopVlan())).collect(Collectors.toSet());
Charles Chandebfea32016-10-24 14:52:01 -0700314 }
Charles Chanee8dbf82017-06-22 14:15:05 -0700315
316 private boolean isReady() {
317 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan910be6a2017-08-23 14:46:43 -0700318 Objects.nonNull(srManager.defaultRoutingHandler);
319 }
320
321 void enqueueRouteEvent(RouteEvent routeEvent) {
322 log.debug("Enqueue routeEvent {}", routeEvent);
323 routeEventCache.put(routeEvent.subject().prefix(), routeEvent);
324 }
325
326 void dequeueRouteEvent(RouteEvent routeEvent) {
327 log.debug("Dequeue routeEvent {}", routeEvent);
328 switch (routeEvent.type()) {
329 case ROUTE_ADDED:
330 processRouteAddedInternal(routeEvent.alternatives());
331 break;
332 case ROUTE_REMOVED:
333 processRouteRemovedInternal(routeEvent.alternatives());
334 break;
335 case ROUTE_UPDATED:
336 case ALTERNATIVE_ROUTES_CHANGED:
337 processRouteUpdatedInternal(Sets.newHashSet(routeEvent.alternatives()),
338 Sets.newHashSet(routeEvent.prevAlternatives()));
339 break;
340 default:
341 break;
342 }
Charles Chanee8dbf82017-06-22 14:15:05 -0700343 }
Charles Chandebfea32016-10-24 14:52:01 -0700344}