blob: c80bc0988acaba0cea8df0172c018deff2d147a9 [file] [log] [blame]
Charles Chan03a73e02016-10-24 14:52:01 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chan03a73e02016-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 Chan2fde6d42017-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 Das7bcbe702017-06-13 15:35:54 -070023import com.google.common.collect.Sets;
24
Charles Chan03a73e02016-10-24 14:52:01 -070025import org.onlab.packet.IpPrefix;
26import org.onlab.packet.MacAddress;
Charles Chan7ffd81f2017-02-08 15:52:08 -080027import org.onlab.packet.VlanId;
Charles Chan9640c812017-08-23 13:55:39 -070028import org.onosproject.net.ConnectPoint;
Charles Chan2fde6d42017-08-23 14:46:43 -070029import org.onosproject.net.HostLocation;
30import org.onosproject.net.PortNumber;
31import org.onosproject.net.host.HostEvent;
Ray Milkey69ec8712017-08-08 13:00:43 -070032import org.onosproject.routeservice.ResolvedRoute;
33import org.onosproject.routeservice.RouteEvent;
Charles Chan03a73e02016-10-24 14:52:01 -070034import org.onosproject.net.DeviceId;
Charles Chan2fde6d42017-08-23 14:46:43 -070035import org.onosproject.routeservice.RouteInfo;
Charles Chan03a73e02016-10-24 14:52:01 -070036import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
Charles Chan2fde6d42017-08-23 14:46:43 -070039import java.util.Collection;
Charles Chanb8664b82017-06-22 14:15:05 -070040import java.util.Objects;
Charles Chan2fde6d42017-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 Chanb8664b82017-06-22 14:15:05 -070046
Charles Chan03a73e02016-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 Chan2fde6d42017-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 Chan9640c812017-08-23 13:55:39 -070078 RouteHandler(SegmentRoutingManager srManager) {
Charles Chan03a73e02016-10-24 14:52:01 -070079 this.srManager = srManager;
Charles Chan2fde6d42017-08-23 14:46:43 -070080
81 Executors.newSingleThreadScheduledExecutor()
82 .scheduleAtFixedRate(routeEventCache::cleanUp, 0, WAIT_TIME_MS, TimeUnit.MILLISECONDS);
Charles Chan03a73e02016-10-24 14:52:01 -070083 }
84
85 protected void init(DeviceId deviceId) {
Charles Chan9640c812017-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 Chan03a73e02016-10-24 14:52:01 -070096 }
97
Charles Chan9640c812017-08-23 13:55:39 -070098 void processRouteAdded(RouteEvent event) {
Charles Chan2fde6d42017-08-23 14:46:43 -070099 enqueueRouteEvent(event);
Charles Chan03a73e02016-10-24 14:52:01 -0700100 }
101
102 private void processRouteAddedInternal(ResolvedRoute route) {
Charles Chan2fde6d42017-08-23 14:46:43 -0700103 processRouteAddedInternal(Sets.newHashSet(route));
104 }
105
106 private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
Charles Chanb8664b82017-06-22 14:15:05 -0700107 if (!isReady()) {
Charles Chan2fde6d42017-08-23 14:46:43 -0700108 log.info("System is not ready. Skip adding route for {}", routes);
Charles Chanb8664b82017-06-22 14:15:05 -0700109 return;
110 }
111
Charles Chan2fde6d42017-08-23 14:46:43 -0700112 log.info("processRouteAddedInternal. routes={}", routes);
Charles Chan9640c812017-08-23 13:55:39 -0700113
Charles Chan2fde6d42017-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 Chan03a73e02016-10-24 14:52:01 -0700122
Charles Chan2fde6d42017-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 Chan03a73e02016-10-24 14:52:01 -0700137 }
138
Charles Chan9640c812017-08-23 13:55:39 -0700139 void processRouteUpdated(RouteEvent event) {
Charles Chan2fde6d42017-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
164
165 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
166 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
167
168 toBeRemoved.forEach(route -> {
169 srManager.nextHopLocations(route).forEach(oldLocation -> {
170 if (toBeAdded.stream().map(srManager::nextHopLocations)
171 .flatMap(Set::stream).map(ConnectPoint::deviceId)
172 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
173 IpPrefix prefix = route.prefix();
174 MacAddress nextHopMac = route.nextHopMac();
175 VlanId nextHopVlan = route.nextHopVlan();
176
177 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
178 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
179 log.debug("RouteUpdated. revokeRoute {}, {}, {}, {}", oldLocation, prefix, nextHopMac, nextHopVlan);
180 srManager.defaultRoutingHandler.revokeRoute(oldLocation.deviceId(), prefix,
181 nextHopMac, nextHopVlan, oldLocation.port());
182 }
183 });
184 });
185
186 toBeAdded.forEach(route -> {
187 IpPrefix prefix = route.prefix();
188 MacAddress nextHopMac = route.nextHopMac();
189 VlanId nextHopVlan = route.nextHopVlan();
190 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
191
192 locations.forEach(location -> {
193 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
194 srManager.deviceConfiguration.addSubnet(location, prefix);
195 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
196 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
197 nextHopMac, nextHopVlan, location.port());
198 });
199 });
200
Charles Chan03a73e02016-10-24 14:52:01 -0700201 }
202
Charles Chan9640c812017-08-23 13:55:39 -0700203 void processRouteRemoved(RouteEvent event) {
Charles Chan2fde6d42017-08-23 14:46:43 -0700204 enqueueRouteEvent(event);
Charles Chan03a73e02016-10-24 14:52:01 -0700205 }
206
Charles Chan2fde6d42017-08-23 14:46:43 -0700207 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanb8664b82017-06-22 14:15:05 -0700208 if (!isReady()) {
Charles Chan2fde6d42017-08-23 14:46:43 -0700209 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanb8664b82017-06-22 14:15:05 -0700210 return;
211 }
212
Charles Chan2fde6d42017-08-23 14:46:43 -0700213 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chan9640c812017-08-23 13:55:39 -0700214
Charles Chan2fde6d42017-08-23 14:46:43 -0700215 Set<IpPrefix> allPrefixes = Sets.newHashSet();
216 routes.forEach(route -> {
217 allPrefixes.add(route.prefix());
218 });
219 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
220 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chan03a73e02016-10-24 14:52:01 -0700221
Charles Chan2fde6d42017-08-23 14:46:43 -0700222 routes.forEach(route -> {
223 IpPrefix prefix = route.prefix();
224 MacAddress nextHopMac = route.nextHopMac();
225 VlanId nextHopVlan = route.nextHopVlan();
226 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
227
228 locations.forEach(location -> {
229 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
230 srManager.deviceConfiguration.removeSubnet(location, prefix);
231 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
232 srManager.defaultRoutingHandler.revokeRoute(location.deviceId(), prefix,
233 nextHopMac, nextHopVlan, location.port());
234
235 // Also remove redirection flows on the pair device if exists.
236 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
237 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
238 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
239 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
240 // when the host is untagged
241 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
242
243 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
244 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
245 nextHopMac, vlanId, pairLocalPort.get());
246 }
247 });
248 });
249 }
250
251 void processHostMovedEvent(HostEvent event) {
252 log.info("processHostMovedEvent {}", event);
253 MacAddress hostMac = event.subject().mac();
254 VlanId hostVlanId = event.subject().vlan();
255
256 affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
257 IpPrefix prefix = affectedRoute.prefix();
258 Set<HostLocation> prevLocations = event.prevSubject().locations();
259 Set<HostLocation> newLocations = event.subject().locations();
260
261 // For each old location
262 Sets.difference(prevLocations, newLocations).stream().filter(srManager::isMasterOf)
263 .forEach(prevLocation -> {
264 // Redirect the flows to pair link if configured
265 // Note: Do not continue removing any rule
266 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
267 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
268 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
269 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
270 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
271 // when the host is untagged
272 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
273 log.debug("HostMoved. populateRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, vlanId);
274 srManager.defaultRoutingHandler.populateRoute(prevLocation.deviceId(), prefix,
275 hostMac, vlanId, pairLocalPort.get());
276 return;
277 }
278
279 // No pair information supplied. Remove route
280 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
281 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
282 hostMac, hostVlanId, prevLocation.port());
283 });
284
285 // For each new location, add all new IPs.
286 Sets.difference(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
287 .forEach(newLocation -> {
288 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
289 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
290 hostMac, hostVlanId, newLocation.port());
291 });
292
293 });
294 }
295
296 private Set<ResolvedRoute> affectedRoutes(MacAddress mac, VlanId vlanId) {
297 return srManager.routeService.getRouteTables().stream()
298 .map(routeTableId -> srManager.routeService.getRoutes(routeTableId))
299 .flatMap(Collection::stream)
300 .map(RouteInfo::allRoutes)
301 .flatMap(Collection::stream)
302 .filter(resolvedRoute -> mac.equals(resolvedRoute.nextHopMac()) &&
303 vlanId.equals(resolvedRoute.nextHopVlan())).collect(Collectors.toSet());
Charles Chan03a73e02016-10-24 14:52:01 -0700304 }
Charles Chanb8664b82017-06-22 14:15:05 -0700305
306 private boolean isReady() {
307 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan2fde6d42017-08-23 14:46:43 -0700308 Objects.nonNull(srManager.defaultRoutingHandler);
309 }
310
311 void enqueueRouteEvent(RouteEvent routeEvent) {
312 log.debug("Enqueue routeEvent {}", routeEvent);
313 routeEventCache.put(routeEvent.subject().prefix(), routeEvent);
314 }
315
316 void dequeueRouteEvent(RouteEvent routeEvent) {
317 log.debug("Dequeue routeEvent {}", routeEvent);
318 switch (routeEvent.type()) {
319 case ROUTE_ADDED:
320 processRouteAddedInternal(routeEvent.alternatives());
321 break;
322 case ROUTE_REMOVED:
323 processRouteRemovedInternal(routeEvent.alternatives());
324 break;
325 case ROUTE_UPDATED:
326 case ALTERNATIVE_ROUTES_CHANGED:
327 processRouteUpdatedInternal(Sets.newHashSet(routeEvent.alternatives()),
328 Sets.newHashSet(routeEvent.prevAlternatives()));
329 break;
330 default:
331 break;
332 }
Charles Chanb8664b82017-06-22 14:15:05 -0700333 }
Charles Chan03a73e02016-10-24 14:52:01 -0700334}