blob: b278b8cc738f69863f361a81214a5d5a353532ea [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 Chan7d20a4e2018-04-13 14:01:49 -040086 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
87
88 srManager.routeService.getRouteTables().stream()
89 .map(srManager.routeService::getRoutes)
90 .flatMap(Collection::stream)
91 .map(RouteInfo::allRoutes)
92 .filter(allRoutes -> allRoutes.stream().allMatch(resolvedRoute ->
93 srManager.nextHopLocations(resolvedRoute).stream().allMatch(cp ->
94 deviceId.equals(cp.deviceId()) ||
95 (pairDeviceId.isPresent() && pairDeviceId.get().equals(cp.deviceId()))
96 )))
97 .forEach(this::processRouteAddedInternal);
Charles Chandebfea32016-10-24 14:52:01 -070098 }
99
Charles Chanf0ae41e2017-08-23 13:55:39 -0700100 void processRouteAdded(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700101 enqueueRouteEvent(event);
Charles Chandebfea32016-10-24 14:52:01 -0700102 }
103
Charles Chan910be6a2017-08-23 14:46:43 -0700104 private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -0700105 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -0700106 log.info("System is not ready. Skip adding route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -0700107 return;
108 }
109
Charles Chan910be6a2017-08-23 14:46:43 -0700110 log.info("processRouteAddedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -0700111
Charles Chan482b6422018-04-09 11:52:08 -0400112 if (routes.size() > 2) {
113 log.info("Route {} has more than two next hops. Do not process route change", routes);
114 return;
115 }
116
Charles Chan910be6a2017-08-23 14:46:43 -0700117 Set<ConnectPoint> allLocations = Sets.newHashSet();
118 Set<IpPrefix> allPrefixes = Sets.newHashSet();
119 routes.forEach(route -> {
120 allLocations.addAll(srManager.nextHopLocations(route));
121 allPrefixes.add(route.prefix());
122 });
123 log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
124 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700125
Charles Chan910be6a2017-08-23 14:46:43 -0700126 routes.forEach(route -> {
127 IpPrefix prefix = route.prefix();
128 MacAddress nextHopMac = route.nextHopMac();
129 VlanId nextHopVlan = route.nextHopVlan();
130 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
131
132 locations.forEach(location -> {
133 log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
134 srManager.deviceConfiguration.addSubnet(location, prefix);
135 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
136 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
137 nextHopMac, nextHopVlan, location.port());
138 });
139 });
Charles Chandebfea32016-10-24 14:52:01 -0700140 }
141
Charles Chanf0ae41e2017-08-23 13:55:39 -0700142 void processRouteUpdated(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700143 enqueueRouteEvent(event);
144 }
145
146 void processAlternativeRoutesChanged(RouteEvent event) {
147 enqueueRouteEvent(event);
148 }
149
150 private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
151 if (!isReady()) {
152 log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
153 return;
154 }
155
156 log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
157
Charles Chan482b6422018-04-09 11:52:08 -0400158 if (routes.size() > 2) {
159 log.info("Route {} has more than two next hops. Do not process route change", routes);
160 return;
161 }
162
Charles Chan910be6a2017-08-23 14:46:43 -0700163 Set<ConnectPoint> allLocations = Sets.newHashSet();
164 Set<IpPrefix> allPrefixes = Sets.newHashSet();
165 routes.forEach(route -> {
166 allLocations.addAll(srManager.nextHopLocations(route));
167 allPrefixes.add(route.prefix());
168 });
Charles Chan482b6422018-04-09 11:52:08 -0400169
170 // Just come back from an invalid next hop count
171 // Revoke subnet from all locations and reset oldRoutes such that system will be reprogrammed from scratch
172 if (oldRoutes.size() > 2) {
173 log.info("Revoke subnet {} and reset oldRoutes");
174 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
175 oldRoutes = Sets.newHashSet();
176 }
177
Charles Chan910be6a2017-08-23 14:46:43 -0700178 log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
179 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
180
Charles Chan910be6a2017-08-23 14:46:43 -0700181 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
182 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
183
184 toBeRemoved.forEach(route -> {
185 srManager.nextHopLocations(route).forEach(oldLocation -> {
Charles Chan06f626c2018-02-05 17:20:05 -0800186 if (toBeAdded.stream().map(srManager::nextHopLocations)
187 .flatMap(Set::stream).map(ConnectPoint::deviceId)
188 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
189 IpPrefix prefix = route.prefix();
190 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
191 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
192 // We don't remove the flow on the old location in occasion of two next hops becoming one
193 // since the populateSubnet will point the old location to the new location via spine.
194 }
Charles Chan910be6a2017-08-23 14:46:43 -0700195 });
196 });
197
198 toBeAdded.forEach(route -> {
199 IpPrefix prefix = route.prefix();
200 MacAddress nextHopMac = route.nextHopMac();
201 VlanId nextHopVlan = route.nextHopVlan();
202 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
203
204 locations.forEach(location -> {
205 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
206 srManager.deviceConfiguration.addSubnet(location, prefix);
207 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
208 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
209 nextHopMac, nextHopVlan, location.port());
210 });
211 });
Charles Chandebfea32016-10-24 14:52:01 -0700212 }
213
Charles Chanf0ae41e2017-08-23 13:55:39 -0700214 void processRouteRemoved(RouteEvent event) {
Charles Chan910be6a2017-08-23 14:46:43 -0700215 enqueueRouteEvent(event);
Charles Chandebfea32016-10-24 14:52:01 -0700216 }
217
Charles Chan910be6a2017-08-23 14:46:43 -0700218 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -0700219 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -0700220 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -0700221 return;
222 }
223
Charles Chan910be6a2017-08-23 14:46:43 -0700224 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -0700225
Charles Chan910be6a2017-08-23 14:46:43 -0700226 Set<IpPrefix> allPrefixes = Sets.newHashSet();
227 routes.forEach(route -> {
228 allPrefixes.add(route.prefix());
229 });
230 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
231 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700232
Charles Chan910be6a2017-08-23 14:46:43 -0700233 routes.forEach(route -> {
234 IpPrefix prefix = route.prefix();
235 MacAddress nextHopMac = route.nextHopMac();
236 VlanId nextHopVlan = route.nextHopVlan();
237 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
238
239 locations.forEach(location -> {
240 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
241 srManager.deviceConfiguration.removeSubnet(location, prefix);
Charles Chanc4d68882018-03-15 16:41:10 -0700242 // We don't need to call revokeRoute again since revokeSubnet will remove the prefix
243 // from all devices, including the ones that next hop attaches to.
Charles Chan910be6a2017-08-23 14:46:43 -0700244
245 // Also remove redirection flows on the pair device if exists.
246 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
247 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
248 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
249 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
250 // when the host is untagged
251 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
252
253 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
254 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
255 nextHopMac, vlanId, pairLocalPort.get());
256 }
257 });
258 });
259 }
260
261 void processHostMovedEvent(HostEvent event) {
262 log.info("processHostMovedEvent {}", event);
263 MacAddress hostMac = event.subject().mac();
264 VlanId hostVlanId = event.subject().vlan();
265
266 affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
267 IpPrefix prefix = affectedRoute.prefix();
268 Set<HostLocation> prevLocations = event.prevSubject().locations();
269 Set<HostLocation> newLocations = event.subject().locations();
270
271 // For each old location
Charles Chand66d6712018-03-29 16:03:41 -0700272 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Charles Chan910be6a2017-08-23 14:46:43 -0700273 // Redirect the flows to pair link if configured
274 // Note: Do not continue removing any rule
275 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
276 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
277 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
278 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
279 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
280 // when the host is untagged
281 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
282 log.debug("HostMoved. populateRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, vlanId);
283 srManager.defaultRoutingHandler.populateRoute(prevLocation.deviceId(), prefix,
284 hostMac, vlanId, pairLocalPort.get());
285 return;
286 }
287
288 // No pair information supplied. Remove route
289 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
290 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
291 hostMac, hostVlanId, prevLocation.port());
292 });
293
294 // For each new location, add all new IPs.
Charles Chand66d6712018-03-29 16:03:41 -0700295 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
Charles Chan910be6a2017-08-23 14:46:43 -0700296 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
297 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
298 hostMac, hostVlanId, newLocation.port());
299 });
300
301 });
302 }
303
304 private Set<ResolvedRoute> affectedRoutes(MacAddress mac, VlanId vlanId) {
305 return srManager.routeService.getRouteTables().stream()
306 .map(routeTableId -> srManager.routeService.getRoutes(routeTableId))
307 .flatMap(Collection::stream)
308 .map(RouteInfo::allRoutes)
309 .flatMap(Collection::stream)
310 .filter(resolvedRoute -> mac.equals(resolvedRoute.nextHopMac()) &&
311 vlanId.equals(resolvedRoute.nextHopVlan())).collect(Collectors.toSet());
Charles Chandebfea32016-10-24 14:52:01 -0700312 }
Charles Chanee8dbf82017-06-22 14:15:05 -0700313
314 private boolean isReady() {
315 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan910be6a2017-08-23 14:46:43 -0700316 Objects.nonNull(srManager.defaultRoutingHandler);
317 }
318
319 void enqueueRouteEvent(RouteEvent routeEvent) {
320 log.debug("Enqueue routeEvent {}", routeEvent);
321 routeEventCache.put(routeEvent.subject().prefix(), routeEvent);
322 }
323
324 void dequeueRouteEvent(RouteEvent routeEvent) {
325 log.debug("Dequeue routeEvent {}", routeEvent);
326 switch (routeEvent.type()) {
327 case ROUTE_ADDED:
328 processRouteAddedInternal(routeEvent.alternatives());
329 break;
330 case ROUTE_REMOVED:
331 processRouteRemovedInternal(routeEvent.alternatives());
332 break;
333 case ROUTE_UPDATED:
334 case ALTERNATIVE_ROUTES_CHANGED:
335 processRouteUpdatedInternal(Sets.newHashSet(routeEvent.alternatives()),
336 Sets.newHashSet(routeEvent.prevAlternatives()));
337 break;
338 default:
339 break;
340 }
Charles Chanee8dbf82017-06-22 14:15:05 -0700341 }
Charles Chandebfea32016-10-24 14:52:01 -0700342}