blob: d9372fe944ddef68768e1b0ccc9cd23d2d3ab1ae [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
Saurav Das7bcbe702017-06-13 15:35:54 -070019import com.google.common.collect.Sets;
20
Charles Chan03a73e02016-10-24 14:52:01 -070021import org.onlab.packet.IpPrefix;
22import org.onlab.packet.MacAddress;
Charles Chan7ffd81f2017-02-08 15:52:08 -080023import org.onlab.packet.VlanId;
Charles Chan9640c812017-08-23 13:55:39 -070024import org.onosproject.net.ConnectPoint;
Charles Chan2fde6d42017-08-23 14:46:43 -070025import org.onosproject.net.HostLocation;
26import org.onosproject.net.PortNumber;
27import org.onosproject.net.host.HostEvent;
Ray Milkey69ec8712017-08-08 13:00:43 -070028import org.onosproject.routeservice.ResolvedRoute;
29import org.onosproject.routeservice.RouteEvent;
Charles Chan03a73e02016-10-24 14:52:01 -070030import org.onosproject.net.DeviceId;
Charles Chan2fde6d42017-08-23 14:46:43 -070031import org.onosproject.routeservice.RouteInfo;
Charles Chan03a73e02016-10-24 14:52:01 -070032import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34
Charles Chan72ff3d92019-04-18 14:30:41 -070035import java.util.List;
Charles Chan2fde6d42017-08-23 14:46:43 -070036import java.util.Collection;
Charles Chanb8664b82017-06-22 14:15:05 -070037import java.util.Objects;
Charles Chan2fde6d42017-08-23 14:46:43 -070038import java.util.Optional;
39import java.util.Set;
Charles Chan2fde6d42017-08-23 14:46:43 -070040import java.util.stream.Collectors;
Charles Chanb8664b82017-06-22 14:15:05 -070041
Charles Chan03a73e02016-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 Chan9640c812017-08-23 13:55:39 -070049 RouteHandler(SegmentRoutingManager srManager) {
Charles Chan03a73e02016-10-24 14:52:01 -070050 this.srManager = srManager;
51 }
52
53 protected void init(DeviceId deviceId) {
Charles Chan2690fac2018-04-13 14:01:49 -040054 srManager.routeService.getRouteTables().stream()
55 .map(srManager.routeService::getRoutes)
56 .flatMap(Collection::stream)
57 .map(RouteInfo::allRoutes)
Charles Chan9797ebb2020-02-14 13:23:57 -080058 .filter(allRoutes -> allRoutes.stream().anyMatch(resolvedRoute ->
59 srManager.nextHopLocations(resolvedRoute).stream()
60 .anyMatch(cp -> deviceId.equals(cp.deviceId()))))
61 .forEach(rr -> processRouteAddedInternal(rr, true));
Charles Chan03a73e02016-10-24 14:52:01 -070062 }
63
Charles Chan9640c812017-08-23 13:55:39 -070064 void processRouteAdded(RouteEvent event) {
Charles Chan9797ebb2020-02-14 13:23:57 -080065 processRouteAddedInternal(event.alternatives(), false);
Charles Chan03a73e02016-10-24 14:52:01 -070066 }
67
Charles Chan9797ebb2020-02-14 13:23:57 -080068 /**
69 * Internal logic that handles route addition.
70 *
71 * @param routes collection of routes to be processed
72 * @param populateRouteOnly true if we only want to populateRoute but not populateSubnet.
73 * Set it to true when initializing a device coming up.
74 * populateSubnet will be done when link comes up later so it is redundant.
75 * populateRoute still needs to be done for statically configured next hop hosts.
76 */
77 private void processRouteAddedInternal(Collection<ResolvedRoute> routes, boolean populateRouteOnly) {
Charles Chanb8664b82017-06-22 14:15:05 -070078 if (!isReady()) {
Charles Chan2fde6d42017-08-23 14:46:43 -070079 log.info("System is not ready. Skip adding route for {}", routes);
Charles Chanb8664b82017-06-22 14:15:05 -070080 return;
81 }
82
Charles Chan2fde6d42017-08-23 14:46:43 -070083 log.info("processRouteAddedInternal. routes={}", routes);
Charles Chan9640c812017-08-23 13:55:39 -070084
Charles Chane7c7d052018-04-09 11:52:08 -040085 if (routes.size() > 2) {
86 log.info("Route {} has more than two next hops. Do not process route change", routes);
87 return;
88 }
89
Charles Chan9797ebb2020-02-14 13:23:57 -080090 ResolvedRoute rr = routes.stream().findFirst().orElse(null);
91 if (rr == null) {
92 log.warn("No resolved route found. Abort processRouteAddedInternal");
93 }
94
Charles Chan2fde6d42017-08-23 14:46:43 -070095 Set<ConnectPoint> allLocations = Sets.newHashSet();
96 Set<IpPrefix> allPrefixes = Sets.newHashSet();
97 routes.forEach(route -> {
98 allLocations.addAll(srManager.nextHopLocations(route));
99 allPrefixes.add(route.prefix());
100 });
101 log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
102 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Charles Chan03a73e02016-10-24 14:52:01 -0700103
Charles Chan9797ebb2020-02-14 13:23:57 -0800104
Charles Chan2fde6d42017-08-23 14:46:43 -0700105 routes.forEach(route -> {
106 IpPrefix prefix = route.prefix();
107 MacAddress nextHopMac = route.nextHopMac();
108 VlanId nextHopVlan = route.nextHopVlan();
109 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
110
111 locations.forEach(location -> {
112 log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
113 srManager.deviceConfiguration.addSubnet(location, prefix);
114 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
115 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000116 nextHopMac, nextHopVlan, location.port(), false);
Charles Chan2fde6d42017-08-23 14:46:43 -0700117 });
118 });
Charles Chan03a73e02016-10-24 14:52:01 -0700119 }
120
Charles Chan9640c812017-08-23 13:55:39 -0700121 void processRouteUpdated(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700122 processRouteUpdatedInternal(Sets.newHashSet(event.alternatives()),
123 Sets.newHashSet(event.prevAlternatives()));
Charles Chan2fde6d42017-08-23 14:46:43 -0700124 }
125
126 void processAlternativeRoutesChanged(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700127 processRouteUpdatedInternal(Sets.newHashSet(event.alternatives()),
128 Sets.newHashSet(event.prevAlternatives()));
Charles Chan2fde6d42017-08-23 14:46:43 -0700129 }
130
131 private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
132 if (!isReady()) {
133 log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
134 return;
135 }
136
137 log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
138
Charles Chane7c7d052018-04-09 11:52:08 -0400139 if (routes.size() > 2) {
140 log.info("Route {} has more than two next hops. Do not process route change", routes);
141 return;
142 }
143
Charles Chan2fde6d42017-08-23 14:46:43 -0700144 Set<ConnectPoint> allLocations = Sets.newHashSet();
145 Set<IpPrefix> allPrefixes = Sets.newHashSet();
146 routes.forEach(route -> {
147 allLocations.addAll(srManager.nextHopLocations(route));
148 allPrefixes.add(route.prefix());
149 });
Charles Chane7c7d052018-04-09 11:52:08 -0400150
151 // Just come back from an invalid next hop count
152 // Revoke subnet from all locations and reset oldRoutes such that system will be reprogrammed from scratch
153 if (oldRoutes.size() > 2) {
154 log.info("Revoke subnet {} and reset oldRoutes");
155 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
156 oldRoutes = Sets.newHashSet();
157 }
158
Charles Chan2fde6d42017-08-23 14:46:43 -0700159 log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
160 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
161
Charles Chan2fde6d42017-08-23 14:46:43 -0700162 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
163 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
164
165 toBeRemoved.forEach(route -> {
166 srManager.nextHopLocations(route).forEach(oldLocation -> {
Charles Chan08d91322018-02-05 17:20:05 -0800167 if (toBeAdded.stream().map(srManager::nextHopLocations)
168 .flatMap(Set::stream).map(ConnectPoint::deviceId)
169 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
170 IpPrefix prefix = route.prefix();
171 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
172 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
173 // We don't remove the flow on the old location in occasion of two next hops becoming one
174 // since the populateSubnet will point the old location to the new location via spine.
175 }
Charles Chan2fde6d42017-08-23 14:46:43 -0700176 });
177 });
178
179 toBeAdded.forEach(route -> {
180 IpPrefix prefix = route.prefix();
181 MacAddress nextHopMac = route.nextHopMac();
182 VlanId nextHopVlan = route.nextHopVlan();
183 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
184
185 locations.forEach(location -> {
186 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
187 srManager.deviceConfiguration.addSubnet(location, prefix);
188 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
189 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000190 nextHopMac, nextHopVlan, location.port(), false);
Charles Chan2fde6d42017-08-23 14:46:43 -0700191 });
192 });
Charles Chan03a73e02016-10-24 14:52:01 -0700193 }
194
Charles Chan9640c812017-08-23 13:55:39 -0700195 void processRouteRemoved(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700196 processRouteRemovedInternal(event.alternatives());
Charles Chan03a73e02016-10-24 14:52:01 -0700197 }
198
Charles Chan2fde6d42017-08-23 14:46:43 -0700199 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanb8664b82017-06-22 14:15:05 -0700200 if (!isReady()) {
Charles Chan2fde6d42017-08-23 14:46:43 -0700201 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanb8664b82017-06-22 14:15:05 -0700202 return;
203 }
204
Charles Chan2fde6d42017-08-23 14:46:43 -0700205 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chan9640c812017-08-23 13:55:39 -0700206
Charles Chan2fde6d42017-08-23 14:46:43 -0700207 Set<IpPrefix> allPrefixes = Sets.newHashSet();
208 routes.forEach(route -> {
209 allPrefixes.add(route.prefix());
210 });
211 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
212 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chan03a73e02016-10-24 14:52:01 -0700213
Charles Chan2fde6d42017-08-23 14:46:43 -0700214 routes.forEach(route -> {
215 IpPrefix prefix = route.prefix();
216 MacAddress nextHopMac = route.nextHopMac();
217 VlanId nextHopVlan = route.nextHopVlan();
218 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
219
220 locations.forEach(location -> {
221 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
222 srManager.deviceConfiguration.removeSubnet(location, prefix);
Charles Chanf433f952018-03-15 16:41:10 -0700223 // We don't need to call revokeRoute again since revokeSubnet will remove the prefix
224 // from all devices, including the ones that next hop attaches to.
Charles Chan2fde6d42017-08-23 14:46:43 -0700225
226 // Also remove redirection flows on the pair device if exists.
227 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
Saurav Das9a554292018-04-27 18:42:30 -0700228 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
Charles Chan2fde6d42017-08-23 14:46:43 -0700229 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
230 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
231 // when the host is untagged
232 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
233
234 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
235 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000236 nextHopMac, vlanId, pairLocalPort.get(), false);
Charles Chan2fde6d42017-08-23 14:46:43 -0700237 }
238 });
239 });
240 }
241
242 void processHostMovedEvent(HostEvent event) {
243 log.info("processHostMovedEvent {}", event);
244 MacAddress hostMac = event.subject().mac();
245 VlanId hostVlanId = event.subject().vlan();
Ruchi Sahotac8aa9f32019-05-09 17:26:14 -0400246
Charles Chan72ff3d92019-04-18 14:30:41 -0700247 Set<HostLocation> prevLocations = event.prevSubject().locations();
248 Set<HostLocation> newLocations = event.subject().locations();
Ruchi Sahotac8aa9f32019-05-09 17:26:14 -0400249 Set<ConnectPoint> connectPoints = newLocations.stream()
250 .map(l -> (ConnectPoint) l).collect(Collectors.toSet());
Charles Chan72ff3d92019-04-18 14:30:41 -0700251 List<Set<IpPrefix>> batchedSubnets =
252 srManager.deviceConfiguration.getBatchedSubnets(event.subject().id());
Ruchi Sahotac8aa9f32019-05-09 17:26:14 -0400253 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
254 .collect(Collectors.toSet());
255
256 // Set of deviceIDs of the previous locations where the host was connected
257 // Used to determine if host moved to different connect points
258 // on same device or moved to a different device altogether
259 Set<DeviceId> oldDeviceIds = prevLocations.stream().map(HostLocation::deviceId)
260 .collect(Collectors.toSet());
261
262 // L3 Ucast bucket needs to be updated only once per host
263 // and only when the no. of routes with the host as next-hop is not zero
264 if (!batchedSubnets.isEmpty()) {
265 // For each new location, if NextObj exists for the host, update with new location ..
266 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
267 int nextId = srManager.getMacVlanNextObjectiveId(newLocation.deviceId(),
268 hostMac, hostVlanId, null, false);
269 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(newLocation)).orElse(hostVlanId);
270
271 if (nextId != -1) {
272 //Update the nextId group bucket
273 log.debug("HostMoved. NextId exists, update L3 Ucast Group Bucket {}, {}, {} --> {}",
274 newLocation, hostMac, vlanId, nextId);
275 srManager.updateMacVlanTreatment(newLocation.deviceId(), hostMac, vlanId,
276 newLocation.port(), nextId);
277 } else {
278 log.debug("HostMoved. NextId does not exist for this location {}, host {}/{}",
279 newLocation, hostMac, vlanId);
280 }
281 });
282 }
Charles Chan2fde6d42017-08-23 14:46:43 -0700283
Charles Chan72ff3d92019-04-18 14:30:41 -0700284 batchedSubnets.forEach(subnets -> {
285 log.debug("HostMoved. populateSubnet {}, {}", newLocations, subnets);
286 srManager.defaultRoutingHandler.populateSubnet(connectPoints, subnets);
Charles Chan2fde6d42017-08-23 14:46:43 -0700287
Charles Chan72ff3d92019-04-18 14:30:41 -0700288 subnets.forEach(prefix -> {
Charles Chan72ff3d92019-04-18 14:30:41 -0700289 // For each old location
290 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000291
Charles Chan72ff3d92019-04-18 14:30:41 -0700292 // Remove flows for unchanged IPs only when the host moves from a switch to another.
293 // Otherwise, do not remove and let the adding part update the old flow
294 if (newDeviceIds.contains(prevLocation.deviceId())) {
295 return;
296 }
Ruchi Sahotaef0761c2019-01-28 01:08:18 +0000297
Charles Chan72ff3d92019-04-18 14:30:41 -0700298 log.debug("HostMoved. removeSubnet {}, {}", prevLocation, prefix);
299 srManager.deviceConfiguration.removeSubnet(prevLocation, prefix);
Charles Chan0f7897f2019-02-28 15:40:57 -0800300
Charles Chan72ff3d92019-04-18 14:30:41 -0700301 // Do not remove flow from a device if the route is still reachable via its pair device.
302 // populateSubnet will update the flow to point to its pair device via spine.
303 DeviceId pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId()).orElse(null);
304 if (newLocations.stream().anyMatch(n -> n.deviceId().equals(pairDeviceId))) {
305 return;
306 }
Charles Chan0f7897f2019-02-28 15:40:57 -0800307
Charles Chan72ff3d92019-04-18 14:30:41 -0700308 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
309 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
310 hostMac, hostVlanId, prevLocation.port(), false);
311 });
Charles Chan0f7897f2019-02-28 15:40:57 -0800312
Charles Chan72ff3d92019-04-18 14:30:41 -0700313 // For each new location, add all new IPs.
314 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
315 log.debug("HostMoved. addSubnet {}, {}", newLocation, prefix);
316 srManager.deviceConfiguration.addSubnet(newLocation, prefix);
317
318 //its a new connect point, not a move from an existing device, populateRoute
319 if (!oldDeviceIds.contains(newLocation.deviceId())) {
320 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
321 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
322 hostMac, hostVlanId, newLocation.port(), false);
Charles Chan72ff3d92019-04-18 14:30:41 -0700323 }
324 });
Charles Chan2fde6d42017-08-23 14:46:43 -0700325 });
Charles Chan2fde6d42017-08-23 14:46:43 -0700326 });
Ruchi Sahotac8aa9f32019-05-09 17:26:14 -0400327
Charles Chan2fde6d42017-08-23 14:46:43 -0700328 }
329
Charles Chanb8664b82017-06-22 14:15:05 -0700330 private boolean isReady() {
331 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan2fde6d42017-08-23 14:46:43 -0700332 Objects.nonNull(srManager.defaultRoutingHandler);
333 }
Charles Chan03a73e02016-10-24 14:52:01 -0700334}