blob: d9309230ab5bddc55832b3c96a454808a6d1e623 [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 Chan2fde6d42017-08-23 14:46:43 -070035import java.util.Collection;
Charles Chanb8664b82017-06-22 14:15:05 -070036import java.util.Objects;
Charles Chan2fde6d42017-08-23 14:46:43 -070037import java.util.Optional;
38import java.util.Set;
Charles Chan2fde6d42017-08-23 14:46:43 -070039import java.util.stream.Collectors;
Charles Chanb8664b82017-06-22 14:15:05 -070040
Charles Chan03a73e02016-10-24 14:52:01 -070041/**
42 * Handles RouteEvent and manages routing entries.
43 */
44public class RouteHandler {
45 private static final Logger log = LoggerFactory.getLogger(RouteHandler.class);
46 private final SegmentRoutingManager srManager;
47
Charles Chan9640c812017-08-23 13:55:39 -070048 RouteHandler(SegmentRoutingManager srManager) {
Charles Chan03a73e02016-10-24 14:52:01 -070049 this.srManager = srManager;
50 }
51
52 protected void init(DeviceId deviceId) {
Charles Chan2690fac2018-04-13 14:01:49 -040053 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
54
55 srManager.routeService.getRouteTables().stream()
56 .map(srManager.routeService::getRoutes)
57 .flatMap(Collection::stream)
58 .map(RouteInfo::allRoutes)
59 .filter(allRoutes -> allRoutes.stream().allMatch(resolvedRoute ->
60 srManager.nextHopLocations(resolvedRoute).stream().allMatch(cp ->
61 deviceId.equals(cp.deviceId()) ||
62 (pairDeviceId.isPresent() && pairDeviceId.get().equals(cp.deviceId()))
63 )))
64 .forEach(this::processRouteAddedInternal);
Charles Chan03a73e02016-10-24 14:52:01 -070065 }
66
Charles Chan9640c812017-08-23 13:55:39 -070067 void processRouteAdded(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -070068 processRouteAddedInternal(event.alternatives());
Charles Chan03a73e02016-10-24 14:52:01 -070069 }
70
Charles Chan2fde6d42017-08-23 14:46:43 -070071 private void processRouteAddedInternal(Collection<ResolvedRoute> routes) {
Charles Chanb8664b82017-06-22 14:15:05 -070072 if (!isReady()) {
Charles Chan2fde6d42017-08-23 14:46:43 -070073 log.info("System is not ready. Skip adding route for {}", routes);
Charles Chanb8664b82017-06-22 14:15:05 -070074 return;
75 }
76
Charles Chan2fde6d42017-08-23 14:46:43 -070077 log.info("processRouteAddedInternal. routes={}", routes);
Charles Chan9640c812017-08-23 13:55:39 -070078
Charles Chane7c7d052018-04-09 11:52:08 -040079 if (routes.size() > 2) {
80 log.info("Route {} has more than two next hops. Do not process route change", routes);
81 return;
82 }
83
Charles Chan2fde6d42017-08-23 14:46:43 -070084 Set<ConnectPoint> allLocations = Sets.newHashSet();
85 Set<IpPrefix> allPrefixes = Sets.newHashSet();
86 routes.forEach(route -> {
87 allLocations.addAll(srManager.nextHopLocations(route));
88 allPrefixes.add(route.prefix());
89 });
90 log.debug("RouteAdded. populateSubnet {}, {}", allLocations, allPrefixes);
91 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
Charles Chan03a73e02016-10-24 14:52:01 -070092
Charles Chan2fde6d42017-08-23 14:46:43 -070093 routes.forEach(route -> {
94 IpPrefix prefix = route.prefix();
95 MacAddress nextHopMac = route.nextHopMac();
96 VlanId nextHopVlan = route.nextHopVlan();
97 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
98
99 locations.forEach(location -> {
100 log.debug("RouteAdded. addSubnet {}, {}", location, prefix);
101 srManager.deviceConfiguration.addSubnet(location, prefix);
102 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
103 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
104 nextHopMac, nextHopVlan, location.port());
105 });
106 });
Charles Chan03a73e02016-10-24 14:52:01 -0700107 }
108
Charles Chan9640c812017-08-23 13:55:39 -0700109 void processRouteUpdated(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700110 processRouteUpdatedInternal(Sets.newHashSet(event.alternatives()),
111 Sets.newHashSet(event.prevAlternatives()));
Charles Chan2fde6d42017-08-23 14:46:43 -0700112 }
113
114 void processAlternativeRoutesChanged(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700115 processRouteUpdatedInternal(Sets.newHashSet(event.alternatives()),
116 Sets.newHashSet(event.prevAlternatives()));
Charles Chan2fde6d42017-08-23 14:46:43 -0700117 }
118
119 private void processRouteUpdatedInternal(Set<ResolvedRoute> routes, Set<ResolvedRoute> oldRoutes) {
120 if (!isReady()) {
121 log.info("System is not ready. Skip updating route for {} -> {}", oldRoutes, routes);
122 return;
123 }
124
125 log.info("processRouteUpdatedInternal. routes={}, oldRoutes={}", routes, oldRoutes);
126
Charles Chane7c7d052018-04-09 11:52:08 -0400127 if (routes.size() > 2) {
128 log.info("Route {} has more than two next hops. Do not process route change", routes);
129 return;
130 }
131
Charles Chan2fde6d42017-08-23 14:46:43 -0700132 Set<ConnectPoint> allLocations = Sets.newHashSet();
133 Set<IpPrefix> allPrefixes = Sets.newHashSet();
134 routes.forEach(route -> {
135 allLocations.addAll(srManager.nextHopLocations(route));
136 allPrefixes.add(route.prefix());
137 });
Charles Chane7c7d052018-04-09 11:52:08 -0400138
139 // Just come back from an invalid next hop count
140 // Revoke subnet from all locations and reset oldRoutes such that system will be reprogrammed from scratch
141 if (oldRoutes.size() > 2) {
142 log.info("Revoke subnet {} and reset oldRoutes");
143 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
144 oldRoutes = Sets.newHashSet();
145 }
146
Charles Chan2fde6d42017-08-23 14:46:43 -0700147 log.debug("RouteUpdated. populateSubnet {}, {}", allLocations, allPrefixes);
148 srManager.defaultRoutingHandler.populateSubnet(allLocations, allPrefixes);
149
Charles Chan2fde6d42017-08-23 14:46:43 -0700150 Set<ResolvedRoute> toBeRemoved = Sets.difference(oldRoutes, routes).immutableCopy();
151 Set<ResolvedRoute> toBeAdded = Sets.difference(routes, oldRoutes).immutableCopy();
152
153 toBeRemoved.forEach(route -> {
154 srManager.nextHopLocations(route).forEach(oldLocation -> {
Charles Chan08d91322018-02-05 17:20:05 -0800155 if (toBeAdded.stream().map(srManager::nextHopLocations)
156 .flatMap(Set::stream).map(ConnectPoint::deviceId)
157 .noneMatch(deviceId -> deviceId.equals(oldLocation.deviceId()))) {
158 IpPrefix prefix = route.prefix();
159 log.debug("RouteUpdated. removeSubnet {}, {}", oldLocation, prefix);
160 srManager.deviceConfiguration.removeSubnet(oldLocation, prefix);
161 // We don't remove the flow on the old location in occasion of two next hops becoming one
162 // since the populateSubnet will point the old location to the new location via spine.
163 }
Charles Chan2fde6d42017-08-23 14:46:43 -0700164 });
165 });
166
167 toBeAdded.forEach(route -> {
168 IpPrefix prefix = route.prefix();
169 MacAddress nextHopMac = route.nextHopMac();
170 VlanId nextHopVlan = route.nextHopVlan();
171 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
172
173 locations.forEach(location -> {
174 log.debug("RouteUpdated. addSubnet {}, {}", location, prefix);
175 srManager.deviceConfiguration.addSubnet(location, prefix);
176 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
177 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
178 nextHopMac, nextHopVlan, location.port());
179 });
180 });
Charles Chan03a73e02016-10-24 14:52:01 -0700181 }
182
Charles Chan9640c812017-08-23 13:55:39 -0700183 void processRouteRemoved(RouteEvent event) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700184 processRouteRemovedInternal(event.alternatives());
Charles Chan03a73e02016-10-24 14:52:01 -0700185 }
186
Charles Chan2fde6d42017-08-23 14:46:43 -0700187 private void processRouteRemovedInternal(Collection<ResolvedRoute> routes) {
Charles Chanb8664b82017-06-22 14:15:05 -0700188 if (!isReady()) {
Charles Chan2fde6d42017-08-23 14:46:43 -0700189 log.info("System is not ready. Skip removing route for {}", routes);
Charles Chanb8664b82017-06-22 14:15:05 -0700190 return;
191 }
192
Charles Chan2fde6d42017-08-23 14:46:43 -0700193 log.info("processRouteRemovedInternal. routes={}", routes);
Charles Chan9640c812017-08-23 13:55:39 -0700194
Charles Chan2fde6d42017-08-23 14:46:43 -0700195 Set<IpPrefix> allPrefixes = Sets.newHashSet();
196 routes.forEach(route -> {
197 allPrefixes.add(route.prefix());
198 });
199 log.debug("RouteRemoved. revokeSubnet {}", allPrefixes);
200 srManager.defaultRoutingHandler.revokeSubnet(allPrefixes);
Charles Chan03a73e02016-10-24 14:52:01 -0700201
Charles Chan2fde6d42017-08-23 14:46:43 -0700202 routes.forEach(route -> {
203 IpPrefix prefix = route.prefix();
204 MacAddress nextHopMac = route.nextHopMac();
205 VlanId nextHopVlan = route.nextHopVlan();
206 Set<ConnectPoint> locations = srManager.nextHopLocations(route);
207
208 locations.forEach(location -> {
209 log.debug("RouteRemoved. removeSubnet {}, {}", location, prefix);
210 srManager.deviceConfiguration.removeSubnet(location, prefix);
Charles Chanf433f952018-03-15 16:41:10 -0700211 // We don't need to call revokeRoute again since revokeSubnet will remove the prefix
212 // from all devices, including the ones that next hop attaches to.
Charles Chan2fde6d42017-08-23 14:46:43 -0700213
214 // Also remove redirection flows on the pair device if exists.
215 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
Saurav Das9a554292018-04-27 18:42:30 -0700216 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(location.deviceId());
Charles Chan2fde6d42017-08-23 14:46:43 -0700217 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
218 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
219 // when the host is untagged
220 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(nextHopVlan);
221
222 log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
223 srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
224 nextHopMac, vlanId, pairLocalPort.get());
225 }
226 });
227 });
228 }
229
230 void processHostMovedEvent(HostEvent event) {
231 log.info("processHostMovedEvent {}", event);
232 MacAddress hostMac = event.subject().mac();
233 VlanId hostVlanId = event.subject().vlan();
234
235 affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
236 IpPrefix prefix = affectedRoute.prefix();
237 Set<HostLocation> prevLocations = event.prevSubject().locations();
238 Set<HostLocation> newLocations = event.subject().locations();
239
Charles Chan50bb6ef2018-04-18 18:41:05 -0700240 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
241 .collect(Collectors.toSet());
242
Charles Chan2fde6d42017-08-23 14:46:43 -0700243 // For each old location
Charles Chan2ff1bac2018-03-29 16:03:41 -0700244 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Charles Chan2fde6d42017-08-23 14:46:43 -0700245 // Redirect the flows to pair link if configured
246 // Note: Do not continue removing any rule
247 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
Saurav Das9a554292018-04-27 18:42:30 -0700248 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPort(prevLocation.deviceId());
Charles Chan2fde6d42017-08-23 14:46:43 -0700249 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
250 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
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(prevLocation)).orElse(hostVlanId);
254 log.debug("HostMoved. populateRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, vlanId);
255 srManager.defaultRoutingHandler.populateRoute(prevLocation.deviceId(), prefix,
256 hostMac, vlanId, pairLocalPort.get());
257 return;
258 }
259
Charles Chan50bb6ef2018-04-18 18:41:05 -0700260 // No pair information supplied.
261 // Remove flows for unchanged IPs only when the host moves from a switch to another.
262 // Otherwise, do not remove and let the adding part update the old flow
263 if (!newDeviceIds.contains(prevLocation.deviceId())) {
264 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
265 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
266 hostMac, hostVlanId, prevLocation.port());
267 }
Charles Chan2fde6d42017-08-23 14:46:43 -0700268 });
269
270 // For each new location, add all new IPs.
Charles Chan2ff1bac2018-03-29 16:03:41 -0700271 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
Charles Chan2fde6d42017-08-23 14:46:43 -0700272 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
273 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
274 hostMac, hostVlanId, newLocation.port());
275 });
276
277 });
278 }
279
280 private Set<ResolvedRoute> affectedRoutes(MacAddress mac, VlanId vlanId) {
281 return srManager.routeService.getRouteTables().stream()
282 .map(routeTableId -> srManager.routeService.getRoutes(routeTableId))
283 .flatMap(Collection::stream)
284 .map(RouteInfo::allRoutes)
285 .flatMap(Collection::stream)
286 .filter(resolvedRoute -> mac.equals(resolvedRoute.nextHopMac()) &&
287 vlanId.equals(resolvedRoute.nextHopVlan())).collect(Collectors.toSet());
Charles Chan03a73e02016-10-24 14:52:01 -0700288 }
Charles Chanb8664b82017-06-22 14:15:05 -0700289
290 private boolean isReady() {
291 return Objects.nonNull(srManager.deviceConfiguration) &&
Charles Chan2fde6d42017-08-23 14:46:43 -0700292 Objects.nonNull(srManager.defaultRoutingHandler);
293 }
Charles Chan03a73e02016-10-24 14:52:01 -0700294}