blob: cc03597f9f3e23c4105cf9fa46479fdb81a23149 [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
Saurav Das261c3002017-06-13 15:35:54 -070019import com.google.common.collect.Sets;
20
Charles Chandebfea32016-10-24 14:52:01 -070021import org.onlab.packet.IpPrefix;
22import org.onlab.packet.MacAddress;
Charles Chan90772a72017-02-08 15:52:08 -080023import org.onlab.packet.VlanId;
Charles Chanf0ae41e2017-08-23 13:55:39 -070024import org.onosproject.net.ConnectPoint;
Charles Chan910be6a2017-08-23 14:46:43 -070025import org.onosproject.net.HostLocation;
26import org.onosproject.net.PortNumber;
27import org.onosproject.net.host.HostEvent;
Ray Milkeya8154312017-08-08 13:00:43 -070028import org.onosproject.routeservice.ResolvedRoute;
29import org.onosproject.routeservice.RouteEvent;
Charles Chandebfea32016-10-24 14:52:01 -070030import org.onosproject.net.DeviceId;
Charles Chan910be6a2017-08-23 14:46:43 -070031import org.onosproject.routeservice.RouteInfo;
Charles Chandebfea32016-10-24 14:52:01 -070032import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34
Charles Chan5eec3b12019-04-18 14:30:41 -070035import java.util.List;
Charles Chan910be6a2017-08-23 14:46:43 -070036import java.util.Collection;
Charles Chanee8dbf82017-06-22 14:15:05 -070037import java.util.Objects;
Charles Chan910be6a2017-08-23 14:46:43 -070038import java.util.Optional;
39import java.util.Set;
Charles Chan910be6a2017-08-23 14:46:43 -070040import java.util.stream.Collectors;
Charles Chanee8dbf82017-06-22 14:15:05 -070041
Charles Chandebfea32016-10-24 14:52:01 -070042/**
43 * Handles RouteEvent and manages routing entries.
44 */
45public class RouteHandler {
46 private static final Logger log = LoggerFactory.getLogger(RouteHandler.class);
47 private final SegmentRoutingManager srManager;
48
Charles Chanf0ae41e2017-08-23 13:55:39 -070049 RouteHandler(SegmentRoutingManager srManager) {
Charles Chandebfea32016-10-24 14:52:01 -070050 this.srManager = srManager;
51 }
52
53 protected void init(DeviceId deviceId) {
Charles Chan7d20a4e2018-04-13 14:01:49 -040054 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
55
56 srManager.routeService.getRouteTables().stream()
57 .map(srManager.routeService::getRoutes)
58 .flatMap(Collection::stream)
59 .map(RouteInfo::allRoutes)
60 .filter(allRoutes -> allRoutes.stream().allMatch(resolvedRoute ->
61 srManager.nextHopLocations(resolvedRoute).stream().allMatch(cp ->
62 deviceId.equals(cp.deviceId()) ||
63 (pairDeviceId.isPresent() && pairDeviceId.get().equals(cp.deviceId()))
64 )))
65 .forEach(this::processRouteAddedInternal);
Charles Chandebfea32016-10-24 14:52:01 -070066 }
67
Charles Chanf0ae41e2017-08-23 13:55:39 -070068 void processRouteAdded(RouteEvent event) {
Charles Chanfbcb8812018-04-18 18:41:05 -070069 processRouteAddedInternal(event.alternatives());
Charles Chandebfea32016-10-24 14:52:01 -070070 }
71
Charles Chan910be6a2017-08-23 14:46:43 -070072 private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -070073 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -070074 log.info("System is not ready. Skip adding route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -070075 return;
76 }
77
Charles Chan910be6a2017-08-23 14:46:43 -070078 log.info("processRouteAddedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -070079
Charles Chan482b6422018-04-09 11:52:08 -040080 if (routes.size() > 2) {
81 log.info("Route {} has more than two next hops. Do not process route change", routes);
82 return;
83 }
84
Charles Chan910be6a2017-08-23 14:46:43 -070085 Set<ConnectPoint> allLocations = Sets.newHashSet();
86 Set<IpPrefix> allPrefixes = Sets.newHashSet();
87 routes.forEach(route -> {
88 allLocations.addAll(srManager.nextHopLocations(route));
89 allPrefixes.add(route.prefix());
90 });
91 log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
92 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -070093
Charles Chan910be6a2017-08-23 14:46:43 -070094 routes.forEach(route -> {
95 IpPrefix prefix = route.prefix();
96 MacAddress nextHopMac = route.nextHopMac();
97 VlanId nextHopVlan = route.nextHopVlan();
98 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
99
100 locations.forEach(location -> {
101 log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
102 srManager.deviceConfiguration.addSubnet(location, prefix);
103 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
104 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +0000105 nextHopMac, nextHopVlan, location.port(), false);
Charles Chan910be6a2017-08-23 14:46:43 -0700106 });
107 });
Charles Chandebfea32016-10-24 14:52:01 -0700108 }
109
Charles Chanf0ae41e2017-08-23 13:55:39 -0700110 void processRouteUpdated(RouteEvent event) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700111 processRouteUpdatedInternal(Sets.newHashSet(event.alternatives()),
112 Sets.newHashSet(event.prevAlternatives()));
Charles Chan910be6a2017-08-23 14:46:43 -0700113 }
114
115 void processAlternativeRoutesChanged(RouteEvent event) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700116 processRouteUpdatedInternal(Sets.newHashSet(event.alternatives()),
117 Sets.newHashSet(event.prevAlternatives()));
Charles Chan910be6a2017-08-23 14:46:43 -0700118 }
119
120 private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
121 if (!isReady()) {
122 log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
123 return;
124 }
125
126 log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
127
Charles Chan482b6422018-04-09 11:52:08 -0400128 if (routes.size() > 2) {
129 log.info("Route {} has more than two next hops. Do not process route change", routes);
130 return;
131 }
132
Charles Chan910be6a2017-08-23 14:46:43 -0700133 Set<ConnectPoint> allLocations = Sets.newHashSet();
134 Set<IpPrefix> allPrefixes = Sets.newHashSet();
135 routes.forEach(route -> {
136 allLocations.addAll(srManager.nextHopLocations(route));
137 allPrefixes.add(route.prefix());
138 });
Charles Chan482b6422018-04-09 11:52:08 -0400139
140 // Just come back from an invalid next hop count
141 // Revoke subnet from all locations and reset oldRoutes such that system will be reprogrammed from scratch
142 if (oldRoutes.size() > 2) {
143 log.info("Revoke subnet {} and reset oldRoutes");
144 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
145 oldRoutes = Sets.newHashSet();
146 }
147
Charles Chan910be6a2017-08-23 14:46:43 -0700148 log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
149 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
150
Charles Chan910be6a2017-08-23 14:46:43 -0700151 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
152 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
153
154 toBeRemoved.forEach(route -> {
155 srManager.nextHopLocations(route).forEach(oldLocation -> {
Charles Chan06f626c2018-02-05 17:20:05 -0800156 if (toBeAdded.stream().map(srManager::nextHopLocations)
157 .flatMap(Set::stream).map(ConnectPoint::deviceId)
158 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
159 IpPrefix prefix = route.prefix();
160 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
161 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
162 // We don't remove the flow on the old location in occasion of two next hops becoming one
163 // since the populateSubnet will point the old location to the new location via spine.
164 }
Charles Chan910be6a2017-08-23 14:46:43 -0700165 });
166 });
167
168 toBeAdded.forEach(route -> {
169 IpPrefix prefix = route.prefix();
170 MacAddress nextHopMac = route.nextHopMac();
171 VlanId nextHopVlan = route.nextHopVlan();
172 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
173
174 locations.forEach(location -> {
175 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
176 srManager.deviceConfiguration.addSubnet(location, prefix);
177 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
178 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +0000179 nextHopMac, nextHopVlan, location.port(), false);
Charles Chan910be6a2017-08-23 14:46:43 -0700180 });
181 });
Charles Chandebfea32016-10-24 14:52:01 -0700182 }
183
Charles Chanf0ae41e2017-08-23 13:55:39 -0700184 void processRouteRemoved(RouteEvent event) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700185 processRouteRemovedInternal(event.alternatives());
Charles Chandebfea32016-10-24 14:52:01 -0700186 }
187
Charles Chan910be6a2017-08-23 14:46:43 -0700188 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanee8dbf82017-06-22 14:15:05 -0700189 if (!isReady()) {
Charles Chan910be6a2017-08-23 14:46:43 -0700190 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanee8dbf82017-06-22 14:15:05 -0700191 return;
192 }
193
Charles Chan910be6a2017-08-23 14:46:43 -0700194 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chanf0ae41e2017-08-23 13:55:39 -0700195
Charles Chan910be6a2017-08-23 14:46:43 -0700196 Set<IpPrefix> allPrefixes = Sets.newHashSet();
197 routes.forEach(route -> {
198 allPrefixes.add(route.prefix());
199 });
200 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
201 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chandebfea32016-10-24 14:52:01 -0700202
Charles Chan910be6a2017-08-23 14:46:43 -0700203 routes.forEach(route -> {
204 IpPrefix prefix = route.prefix();
205 MacAddress nextHopMac = route.nextHopMac();
206 VlanId nextHopVlan = route.nextHopVlan();
207 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
208
209 locations.forEach(location -> {
210 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
211 srManager.deviceConfiguration.removeSubnet(location, prefix);
Charles Chanc4d68882018-03-15 16:41:10 -0700212 // We don't need to call revokeRoute again since revokeSubnet will remove the prefix
213 // from all devices, including the ones that next hop attaches to.
Charles Chan910be6a2017-08-23 14:46:43 -0700214
215 // Also remove redirection flows on the pair device if exists.
216 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
Saurav Dasec683dc2018-04-27 18:42:30 -0700217 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
Charles Chan910be6a2017-08-23 14:46:43 -0700218 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
219 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
220 // when the host is untagged
221 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
222
223 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
224 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +0000225 nextHopMac, vlanId, pairLocalPort.get(), false);
Charles Chan910be6a2017-08-23 14:46:43 -0700226 }
227 });
228 });
229 }
230
231 void processHostMovedEvent(HostEvent event) {
232 log.info("processHostMovedEvent {}", event);
233 MacAddress hostMac = event.subject().mac();
234 VlanId hostVlanId = event.subject().vlan();
Ruchi Sahota07869322019-05-09 17:26:14 -0400235
Charles Chan5eec3b12019-04-18 14:30:41 -0700236 Set<HostLocation> prevLocations = event.prevSubject().locations();
237 Set<HostLocation> newLocations = event.subject().locations();
Ruchi Sahota07869322019-05-09 17:26:14 -0400238 Set<ConnectPoint> connectPoints = newLocations.stream()
239 .map(l -> (ConnectPoint) l).collect(Collectors.toSet());
Charles Chan5eec3b12019-04-18 14:30:41 -0700240 List<Set<IpPrefix>> batchedSubnets =
241 srManager.deviceConfiguration.getBatchedSubnets(event.subject().id());
Ruchi Sahota07869322019-05-09 17:26:14 -0400242 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
243 .collect(Collectors.toSet());
244
245 // Set of deviceIDs of the previous locations where the host was connected
246 // Used to determine if host moved to different connect points
247 // on same device or moved to a different device altogether
248 Set<DeviceId> oldDeviceIds = prevLocations.stream().map(HostLocation::deviceId)
249 .collect(Collectors.toSet());
250
251 // L3 Ucast bucket needs to be updated only once per host
252 // and only when the no. of routes with the host as next-hop is not zero
253 if (!batchedSubnets.isEmpty()) {
254 // For each new location, if NextObj exists for the host, update with new location ..
255 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
256 int nextId = srManager.getMacVlanNextObjectiveId(newLocation.deviceId(),
257 hostMac, hostVlanId, null, false);
258 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(newLocation)).orElse(hostVlanId);
259
260 if (nextId != -1) {
261 //Update the nextId group bucket
262 log.debug("HostMoved. NextId exists, update L3 Ucast Group Bucket {}, {}, {} --> {}",
263 newLocation, hostMac, vlanId, nextId);
264 srManager.updateMacVlanTreatment(newLocation.deviceId(), hostMac, vlanId,
265 newLocation.port(), nextId);
266 } else {
267 log.debug("HostMoved. NextId does not exist for this location {}, host {}/{}",
268 newLocation, hostMac, vlanId);
269 }
270 });
271 }
Charles Chan910be6a2017-08-23 14:46:43 -0700272
Charles Chan5eec3b12019-04-18 14:30:41 -0700273 batchedSubnets.forEach(subnets -> {
274 log.debug("HostMoved. populateSubnet {}, {}", newLocations, subnets);
275 srManager.defaultRoutingHandler.populateSubnet(connectPoints, subnets);
Charles Chan910be6a2017-08-23 14:46:43 -0700276
Charles Chan5eec3b12019-04-18 14:30:41 -0700277 subnets.forEach(prefix -> {
Charles Chan5eec3b12019-04-18 14:30:41 -0700278 // For each old location
279 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +0000280
Charles Chan5eec3b12019-04-18 14:30:41 -0700281 // Remove flows for unchanged IPs only when the host moves from a switch to another.
282 // Otherwise, do not remove and let the adding part update the old flow
283 if (newDeviceIds.contains(prevLocation.deviceId())) {
284 return;
285 }
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +0000286
Charles Chan5eec3b12019-04-18 14:30:41 -0700287 log.debug("HostMoved. removeSubnet {}, {}", prevLocation, prefix);
288 srManager.deviceConfiguration.removeSubnet(prevLocation, prefix);
Charles Chan4dcd5102019-02-28 15:40:57 -0800289
Charles Chan5eec3b12019-04-18 14:30:41 -0700290 // Do not remove flow from a device if the route is still reachable via its pair device.
291 // populateSubnet will update the flow to point to its pair device via spine.
292 DeviceId pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId()).orElse(null);
293 if (newLocations.stream().anyMatch(n -> n.deviceId().equals(pairDeviceId))) {
294 return;
295 }
Charles Chan4dcd5102019-02-28 15:40:57 -0800296
Charles Chan5eec3b12019-04-18 14:30:41 -0700297 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
298 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
299 hostMac, hostVlanId, prevLocation.port(), false);
300 });
Charles Chan4dcd5102019-02-28 15:40:57 -0800301
Charles Chan5eec3b12019-04-18 14:30:41 -0700302 // For each new location, add all new IPs.
303 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
304 log.debug("HostMoved. addSubnet {}, {}", newLocation, prefix);
305 srManager.deviceConfiguration.addSubnet(newLocation, prefix);
306
307 //its a new connect point, not a move from an existing device, populateRoute
308 if (!oldDeviceIds.contains(newLocation.deviceId())) {
309 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
310 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
311 hostMac, hostVlanId, newLocation.port(), false);
Charles Chan5eec3b12019-04-18 14:30:41 -0700312 }
313 });
Charles Chan910be6a2017-08-23 14:46:43 -0700314 });
Charles Chan910be6a2017-08-23 14:46:43 -0700315 });
Ruchi Sahota07869322019-05-09 17:26:14 -0400316
Charles Chan910be6a2017-08-23 14:46:43 -0700317 }
318
Charles Chanee8dbf82017-06-22 14:15:05 -0700319 private boolean isReady() {
320 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan910be6a2017-08-23 14:46:43 -0700321 Objects.nonNull(srManager.defaultRoutingHandler);
322 }
Charles Chandebfea32016-10-24 14:52:01 -0700323}