blob: e9517f1e1e04bbf670e9a6a40efeac0fe2d7d209 [file] [log] [blame]
sanghob35a6192015-04-01 13:05:26 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sanghob35a6192015-04-01 13:05:26 -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 */
16package org.onosproject.segmentrouting;
17
Saurav Dasd2fded02016-12-02 15:43:47 -080018import com.google.common.base.MoreObjects;
Saurav Dasc88d4662017-05-15 15:34:25 -070019import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableMap.Builder;
Charles Chan93e71ba2016-04-29 14:38:22 -070021import com.google.common.collect.ImmutableSet;
Saurav Das4e3224f2016-11-29 14:27:25 -080022import com.google.common.collect.Lists;
sangho20eff1d2015-04-13 15:15:58 -070023import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
Saurav Dasceccf242017-08-03 18:30:35 -070025
26import org.joda.time.DateTime;
sangho666cd6d2015-04-14 16:27:13 -070027import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080028import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070029import org.onlab.packet.IpPrefix;
Saurav Das7bcbe702017-06-13 15:35:54 -070030import org.onosproject.cluster.NodeId;
Charles Chan93e71ba2016-04-29 14:38:22 -070031import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070032import org.onosproject.net.Device;
33import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070034import org.onosproject.net.Link;
Charles Chan0b4e6182015-11-03 10:42:14 -080035import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
36import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070037import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
sanghob35a6192015-04-01 13:05:26 -070038import org.slf4j.Logger;
39import org.slf4j.LoggerFactory;
40
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.HashSet;
Saurav Das7bcbe702017-06-13 15:35:54 -070044import java.util.Iterator;
45import java.util.Map;
Saurav Dasd2fded02016-12-02 15:43:47 -080046import java.util.Objects;
sanghob35a6192015-04-01 13:05:26 -070047import java.util.Set;
Saurav Das59232cf2016-04-27 18:35:50 -070048import java.util.concurrent.ScheduledExecutorService;
49import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090050import java.util.concurrent.locks.Lock;
51import java.util.concurrent.locks.ReentrantLock;
sanghob35a6192015-04-01 13:05:26 -070052
Saurav Dasd2fded02016-12-02 15:43:47 -080053import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventree0ae7a32016-11-23 09:57:42 -080054import static com.google.common.base.Preconditions.checkNotNull;
55import static java.util.concurrent.Executors.newScheduledThreadPool;
56import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070057
Charles Chane849c192016-01-11 18:28:54 -080058/**
59 * Default routing handler that is responsible for route computing and
60 * routing rule population.
61 */
sanghob35a6192015-04-01 13:05:26 -070062public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080063 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
64 private static final int RETRY_INTERVAL_MS = 250;
65 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070066 private static final long STABLITY_THRESHOLD = 10; //secs
Charles Chan93e71ba2016-04-29 14:38:22 -070067 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070068
69 private SegmentRoutingManager srManager;
70 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053071 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
72 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070073 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090074 private final Lock statusLock = new ReentrantLock();
75 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070076 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080077 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Dasceccf242017-08-03 18:30:35 -070078 private DateTime lastRoutingChange;
sanghob35a6192015-04-01 13:05:26 -070079
80 /**
81 * Represents the default routing population status.
82 */
83 public enum Status {
84 // population process is not started yet.
85 IDLE,
86
87 // population process started.
88 STARTED,
89
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -070090 // population process was aborted due to errors, mostly for groups not
91 // found.
sanghob35a6192015-04-01 13:05:26 -070092 ABORTED,
93
94 // population process was finished successfully.
95 SUCCEEDED
96 }
97
98 /**
99 * Creates a DefaultRoutingHandler object.
100 *
101 * @param srManager SegmentRoutingManager object
102 */
103 public DefaultRoutingHandler(SegmentRoutingManager srManager) {
104 this.srManager = srManager;
105 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700106 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700107 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700108 this.currentEcmpSpgMap = Maps.newHashMap();
sanghob35a6192015-04-01 13:05:26 -0700109 }
110
111 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700112 * Returns an immutable copy of the current ECMP shortest-path graph as
113 * computed by this controller instance.
114 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700115 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700116 */
117 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
118 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
119 currentEcmpSpgMap.entrySet().forEach(entry -> {
120 if (entry.getValue() != null) {
121 builder.put(entry.getKey(), entry.getValue());
122 }
123 });
124 return builder.build();
125 }
126
Saurav Dasceccf242017-08-03 18:30:35 -0700127 /**
128 * Acquires the lock used when making routing changes.
129 */
130 public void acquireRoutingLock() {
131 statusLock.lock();
132 }
133
134 /**
135 * Releases the lock used when making routing changes.
136 */
137 public void releaseRoutingLock() {
138 statusLock.unlock();
139 }
140
141 /**
142 * Determines if routing in the network has been stable in the last
143 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
144 * routing change timestamp.
145 *
146 * @return true if stable
147 */
148 public boolean isRoutingStable() {
149 long last = (long) (lastRoutingChange.getMillis() / 1000.0);
150 long now = (long) (DateTime.now().getMillis() / 1000.0);
151 log.debug("Routing stable since {}s", now - last);
152 return (now - last) > STABLITY_THRESHOLD;
153 }
154
155
Saurav Das7bcbe702017-06-13 15:35:54 -0700156 //////////////////////////////////////
157 // Route path handling
158 //////////////////////////////////////
159
160 /* The following three methods represent the three major ways in routing
161 * is triggered in the network
162 * a) due to configuration change
163 * b) due to route-added event
164 * c) due to change in the topology
165 */
166
Saurav Dasc88d4662017-05-15 15:34:25 -0700167 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700168 * Populates all routing rules to all switches. Typically triggered at
169 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700170 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700171 public void populateAllRoutingRules() {
Saurav Dasceccf242017-08-03 18:30:35 -0700172 lastRoutingChange = DateTime.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900173 statusLock.lock();
174 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700175 if (populationStatus == Status.STARTED) {
176 log.warn("Previous rule population is not finished. Cannot"
177 + " proceed with populateAllRoutingRules");
178 return;
179 }
180
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900181 populationStatus = Status.STARTED;
182 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700183 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900184 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700185
Saurav Das7bcbe702017-06-13 15:35:54 -0700186 // take a snapshot of the topology
187 updatedEcmpSpgMap = new HashMap<>();
188 Set<EdgePair> edgePairs = new HashSet<>();
189 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
190 for (Device dstSw : srManager.deviceService.getDevices()) {
191 EcmpShortestPathGraph ecmpSpgUpdated =
192 new EcmpShortestPathGraph(dstSw.id(), srManager);
193 updatedEcmpSpgMap.put(dstSw.id(), ecmpSpgUpdated);
194 DeviceId pairDev = getPairDev(dstSw.id());
195 if (pairDev != null) {
196 // pairDev may not be available yet, but we still need to add
197 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
198 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
199 edgePairs.add(new EdgePair(dstSw.id(), pairDev));
200 }
201 DeviceId ret = shouldHandleRouting(dstSw.id());
202 if (ret == null) {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900203 continue;
204 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700205 Set<DeviceId> devsToProcess = Sets.newHashSet(dstSw.id(), ret);
206 // To do a full reroute, assume all routes have changed
207 for (DeviceId dev : devsToProcess) {
208 for (Device targetSw : srManager.deviceService.getDevices()) {
209 if (targetSw.id().equals(dev)) {
210 continue;
211 }
212 routeChanges.add(Lists.newArrayList(targetSw.id(), dev));
213 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900214 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700215 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900216
Saurav Das7bcbe702017-06-13 15:35:54 -0700217 if (!redoRouting(routeChanges, edgePairs, null)) {
218 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
219 populationStatus = Status.ABORTED;
220 log.warn("Failed to repopulate all routing rules.");
221 return;
sanghob35a6192015-04-01 13:05:26 -0700222 }
223
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900224 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
225 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700226 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900227 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700228 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900229 } finally {
230 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700231 }
sanghob35a6192015-04-01 13:05:26 -0700232 }
233
sangho20eff1d2015-04-13 15:15:58 -0700234 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700235 * Populate rules from all other edge devices to the connect-point(s)
236 * specified for the given subnets.
237 *
238 * @param cpts connect point(s) of the subnets being added
239 * @param subnets subnets being added
240 */ //XXX refactor
241 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Saurav Dasceccf242017-08-03 18:30:35 -0700242 lastRoutingChange = DateTime.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700243 statusLock.lock();
244 try {
245 if (populationStatus == Status.STARTED) {
246 log.warn("Previous rule population is not finished. Cannot"
247 + " proceed with routing rules for added routes");
248 return;
249 }
250 populationStatus = Status.STARTED;
251 rulePopulator.resetCounter();
252 log.info("Starting to populate routing rules for added routes");
253 // Take snapshots of the topology
254 updatedEcmpSpgMap = new HashMap<>();
255 Set<EdgePair> edgePairs = new HashSet<>();
256 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
257 boolean handleRouting = false;
258
259 if (cpts.size() == 2) {
260 // ensure connect points are edge-pairs
261 Iterator<ConnectPoint> iter = cpts.iterator();
262 DeviceId dev1 = iter.next().deviceId();
263 DeviceId pairDev = getPairDev(dev1);
264 if (iter.next().deviceId().equals(pairDev)) {
265 edgePairs.add(new EdgePair(dev1, pairDev));
266 } else {
267 log.warn("Connectpoints {} for subnets {} not on "
268 + "pair-devices.. aborting populateSubnet", cpts, subnets);
269 populationStatus = Status.ABORTED;
270 return;
271 }
272 for (ConnectPoint cp : cpts) {
273 EcmpShortestPathGraph ecmpSpgUpdated =
274 new EcmpShortestPathGraph(cp.deviceId(), srManager);
275 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
276 DeviceId retId = shouldHandleRouting(cp.deviceId());
277 if (retId == null) {
278 continue;
279 }
280 handleRouting = true;
281 }
282 } else {
283 // single connect point
284 DeviceId dstSw = cpts.iterator().next().deviceId();
285 EcmpShortestPathGraph ecmpSpgUpdated =
286 new EcmpShortestPathGraph(dstSw, srManager);
287 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
288 if (srManager.mastershipService.isLocalMaster(dstSw)) {
289 handleRouting = true;
290 }
291 }
292
293 if (!handleRouting) {
294 log.debug("This instance is not handling ecmp routing to the "
295 + "connectPoint(s) {}", cpts);
296 populationStatus = Status.ABORTED;
297 return;
298 }
299
300 // if it gets here, this instance should handle routing for the
301 // connectpoint(s). Assume all route-paths have to be updated to
302 // the connectpoint(s) with the following exceptions
303 // 1. if target is non-edge no need for routing rules
304 // 2. if target is one of the connectpoints
305 for (ConnectPoint cp : cpts) {
306 DeviceId dstSw = cp.deviceId();
307 for (Device targetSw : srManager.deviceService.getDevices()) {
308 boolean isEdge = false;
309 try {
310 isEdge = config.isEdgeDevice(targetSw.id());
311 } catch (DeviceConfigNotFoundException e) {
312 log.warn(e.getMessage() + "aborting populateSubnet");
313 populationStatus = Status.ABORTED;
314 return;
315 }
316 if (dstSw.equals(targetSw.id()) || !isEdge ||
317 (cpts.size() == 2 &&
318 targetSw.id().equals(getPairDev(dstSw)))) {
319 continue;
320 }
321 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
322 }
323 }
324
325 if (!redoRouting(routeChanges, edgePairs, subnets)) {
326 log.debug("populateSubnet: populationStatus is ABORTED");
327 populationStatus = Status.ABORTED;
328 log.warn("Failed to repopulate the rules for subnet.");
329 return;
330 }
331
332 log.debug("populateSubnet: populationStatus is SUCCEEDED");
333 populationStatus = Status.SUCCEEDED;
334 log.info("Completed subnet population. Total # of rules pushed : {}",
335 rulePopulator.getCounter());
336 return;
337
338 } finally {
339 statusLock.unlock();
340 }
341 }
342
343 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700344 * Populates the routing rules or makes hash group changes according to the
345 * route-path changes due to link failure, switch failure or link up. This
346 * method should only be called for one of these three possible event-types.
347 * Note that when a switch goes away, all of its links fail as well,
348 * but this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700349 *
Saurav Dasc88d4662017-05-15 15:34:25 -0700350 * @param linkDown the single failed link, or null for other conditions
351 * such as link-up or a removed switch
352 * @param linkUp the single link up, or null for other conditions such as
353 * link-down or a removed switch
354 * @param switchDown the removed switch, or null for other conditions such as
355 * link-down or link-up
Saurav Das7bcbe702017-06-13 15:35:54 -0700356 */ // refactor
Saurav Dasc88d4662017-05-15 15:34:25 -0700357 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
358 Link linkUp,
359 DeviceId switchDown) {
360 if ((linkDown != null && (linkUp != null || switchDown != null)) ||
361 (linkUp != null && (linkDown != null || switchDown != null)) ||
362 (switchDown != null && (linkUp != null || linkDown != null))) {
363 log.warn("Only one event can be handled for link status change .. aborting");
364 return;
365 }
Saurav Dasceccf242017-08-03 18:30:35 -0700366 lastRoutingChange = DateTime.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900367 statusLock.lock();
368 try {
sangho20eff1d2015-04-13 15:15:58 -0700369
370 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700371 log.warn("Previous rule population is not finished. Cannot"
372 + " proceeed with routingRules for Link Status change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700373 return;
sangho20eff1d2015-04-13 15:15:58 -0700374 }
375
Saurav Das7bcbe702017-06-13 15:35:54 -0700376 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700377 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700378 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700379 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530380 EcmpShortestPathGraph ecmpSpgUpdated =
381 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700382 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Saurav Das7bcbe702017-06-13 15:35:54 -0700383 DeviceId pairDev = getPairDev(sw.id());
384 if (pairDev != null) {
385 // pairDev may not be available yet, but we still need to add
386 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
387 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
388 edgePairs.add(new EdgePair(sw.id(), pairDev));
389 }
sangho45b009c2015-05-07 13:30:57 -0700390 }
391
Saurav Das7bcbe702017-06-13 15:35:54 -0700392 log.info("Starting to populate routing rules from link status change");
sangho52abe3a2015-05-05 14:13:34 -0700393
sangho20eff1d2015-04-13 15:15:58 -0700394 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700395 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700396 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700397 populationStatus = Status.STARTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700398 rulePopulator.resetCounter();
Saurav Das4e3224f2016-11-29 14:27:25 -0800399 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700400 if (linkDown == null) {
401 // either a linkUp or a switchDown - compute all route changes by
402 // comparing all routes of existing ECMP SPG to new ECMP SPG
sangho20eff1d2015-04-13 15:15:58 -0700403 routeChanges = computeRouteChange();
Saurav Dasc88d4662017-05-15 15:34:25 -0700404
405 if (routeChanges != null) {
406 // deal with linkUp of a seen-before link
407 if (linkUp != null && srManager.isSeenLink(linkUp)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700408 if (!srManager.isBidirectional(linkUp)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700409 log.warn("Not a bidirectional link yet .. not "
410 + "processing link {}", linkUp);
411 srManager.updateSeenLink(linkUp, true);
412 populationStatus = Status.ABORTED;
413 return;
414 }
415 // link previously seen before
416 // do hash-bucket changes instead of a re-route
417 processHashGroupChange(routeChanges, false, null);
418 // clear out routesChanges so a re-route is not attempted
419 routeChanges = ImmutableSet.of();
420 }
421
422 //deal with switchDown
423 if (switchDown != null) {
424 processHashGroupChange(routeChanges, true, switchDown);
425 // clear out routesChanges so a re-route is not attempted
426 routeChanges = ImmutableSet.of();
427 }
428
429 // for a linkUp of a never-seen-before link
430 // let it fall through to a reroute of the routeChanges
431
432 }
433
434 // now that we are past the check for a previously seen link
435 // it is safe to update the store for the linkUp
436 if (linkUp != null) {
437 srManager.updateSeenLink(linkUp, true);
438 }
439
sangho20eff1d2015-04-13 15:15:58 -0700440 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700441 // link has gone down
442 // Compare existing ECMP SPG only with the link that went down
443 routeChanges = computeDamagedRoutes(linkDown);
444 if (routeChanges != null) {
445 processHashGroupChange(routeChanges, true, null);
446 // clear out routesChanges so a re-route is not attempted
447 routeChanges = ImmutableSet.of();
448 }
sangho20eff1d2015-04-13 15:15:58 -0700449 }
450
Saurav Das4e3224f2016-11-29 14:27:25 -0800451 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700452 if (routeChanges == null) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700453 log.info("Optimized routing failed... opting for full reroute");
454 populationStatus = Status.ABORTED;
455 statusLock.unlock();
Saurav Dasc88d4662017-05-15 15:34:25 -0700456 populateAllRoutingRules();
457 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700458 }
459
sangho20eff1d2015-04-13 15:15:58 -0700460 if (routeChanges.isEmpty()) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700461 log.info("No re-route attempted for the link status change");
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700462 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700463 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700464 return;
sangho20eff1d2015-04-13 15:15:58 -0700465 }
466
Saurav Dasc88d4662017-05-15 15:34:25 -0700467 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700468 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700469 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700470 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700471 log.info("Completed repopulation of rules for link-status change."
472 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700473 return;
sangho20eff1d2015-04-13 15:15:58 -0700474 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700475 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700476 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700477 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700478 return;
sangho20eff1d2015-04-13 15:15:58 -0700479 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900480 } finally {
481 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700482 }
483 }
484
Saurav Dasc88d4662017-05-15 15:34:25 -0700485 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700486 * Processes a set a route-path changes by reprogramming routing rules and
487 * creating new hash-groups or editing them if necessary. This method also
488 * determines the next-hops for the route-path from the src-switch (target)
489 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700490 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700491 * @param routeChanges a set of route-path changes, where each route-path is
492 * a list with its first element the src-switch (target)
493 * of the path, and the second element the dst-switch of
494 * the path.
495 * @param edgePairs a set of edge-switches that are paired by configuration
496 * @param subnets a set of prefixes that need to be populated in the routing
497 * table of the target switch in the route-path. Can be null,
498 * in which case all the prefixes belonging to the dst-switch
499 * will be populated in the target switch
500 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700501 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700502 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
503 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
504 // first make every entry two-elements
505 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
506 for (ArrayList<DeviceId> route : routeChanges) {
507 if (route.size() == 1) {
508 DeviceId dstSw = route.get(0);
509 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
510 if (ec == null) {
511 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
512 return false;
513 }
514 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
515 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
516 changedRoutes.add(Lists.newArrayList(target, dstSw));
517 });
518 });
519 } else {
520 DeviceId targetSw = route.get(0);
521 DeviceId dstSw = route.get(1);
522 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
523 }
524 }
525
526 // now process changedRoutes according to edgePairs
527 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
528 return false; //abort routing and fail fast
529 }
530
531 // whatever is left in changedRoutes is now processed for individual dsts.
532 if (!redoRoutingIndividualDests(subnets, changedRoutes)) {
533 return false; //abort routing and fail fast
534 }
535
536 //XXX should we do hashgroupchanges here?
537
538 // update ecmpSPG for all edge-pairs
539 for (EdgePair ep : edgePairs) {
540 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
541 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
542 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
543 }
544 return true;
545 }
546
547 /**
548 * Programs targetSw in the changedRoutes for given prefixes reachable by
549 * an edgePair. If no prefixes are given, the method will use configured
550 * subnets/prefixes. If some configured subnets belong only to a specific
551 * destination in the edgePair, then the target switch will be programmed
552 * only to that destination.
553 *
554 * @param edgePairs set of edge-pairs for which target will be programmed
555 * @param subnets a set of prefixes that need to be populated in the routing
556 * table of the target switch in the changedRoutes. Can be null,
557 * in which case all the configured prefixes belonging to the
558 * paired switches will be populated in the target switch
559 * @param changedRoutes a set of route-path changes, where each route-path is
560 * a list with its first element the src-switch (target)
561 * of the path, and the second element the dst-switch of
562 * the path.
563 * @return true if successful
564 */
565 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
566 Set<IpPrefix> subnets,
567 Set<ArrayList<DeviceId>> changedRoutes) {
568 for (EdgePair ep : edgePairs) {
569 // temp store for a target's changedRoutes to this edge-pair
570 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
571 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
572 while (i.hasNext()) {
573 ArrayList<DeviceId> route = i.next();
574 DeviceId dstSw = route.get(1);
575 if (ep.includes(dstSw)) {
576 // routeChange for edge pair found
577 // sort by target iff target is edge and remove from changedRoutes
578 DeviceId targetSw = route.get(0);
579 try {
580 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
581 continue;
582 }
583 } catch (DeviceConfigNotFoundException e) {
584 log.warn(e.getMessage() + "aborting redoRouting");
585 return false;
586 }
587 // route is from another edge to this edge-pair
588 if (targetRoutes.containsKey(targetSw)) {
589 targetRoutes.get(targetSw).add(route);
590 } else {
591 Set<ArrayList<DeviceId>> temp = new HashSet<>();
592 temp.add(route);
593 targetRoutes.put(targetSw, temp);
594 }
595 i.remove();
596 }
597 }
598 // so now for this edgepair we have a per target set of routechanges
599 // process target->edgePair route
600 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
601 targetRoutes.entrySet()) {
602 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
603 entry.getKey(), ep);
604 DeviceId targetSw = entry.getKey();
605 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
606 entry.getValue().forEach(route -> {
607 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
608 log.debug("route: target {} -> dst {} found with next-hops {}",
609 route.get(0), route.get(1), nhops);
610 perDstNextHops.put(route.get(1), nhops);
611 });
612 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
613 : subnets;
614 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
615 : subnets;
616 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
617 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
618 // handle routing to subnets common to edge-pair
619 // only if the targetSw is not part of the edge-pair
620 if (!ep.includes(targetSw)) {
621 if (!populateEcmpRoutingRulePartial(
622 targetSw,
623 ep.dev1, ep.dev2,
624 perDstNextHops,
625 Sets.intersection(ipDev1, ipDev2))) {
626 return false; // abort everything and fail fast
627 }
628 }
629 // handle routing to subnets that only belong to dev1
630 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
631 if (!onlyDev1Subnets.isEmpty() && perDstNextHops.get(ep.dev1) != null) {
632 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
633 onlyDev1NextHops.put(ep.dev1, perDstNextHops.get(ep.dev1));
634 if (!populateEcmpRoutingRulePartial(
635 targetSw,
636 ep.dev1, null,
637 onlyDev1NextHops,
638 onlyDev1Subnets)) {
639 return false; // abort everything and fail fast
640 }
641 }
642 // handle routing to subnets that only belong to dev2
643 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
644 if (!onlyDev2Subnets.isEmpty() && perDstNextHops.get(ep.dev2) != null) {
645 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
646 onlyDev2NextHops.put(ep.dev2, perDstNextHops.get(ep.dev2));
647 if (!populateEcmpRoutingRulePartial(
648 targetSw,
649 ep.dev2, null,
650 onlyDev2NextHops,
651 onlyDev2Subnets)) {
652 return false; // abort everything and fail fast
653 }
654 }
655 }
656 // if it gets here it has succeeded for all targets to this edge-pair
657 }
658 return true;
659 }
660
661 /**
662 * Programs targetSw in the changedRoutes for given prefixes reachable by
663 * a destination switch that is not part of an edge-pair.
664 * If no prefixes are given, the method will use configured subnets/prefixes.
665 *
666 * @param subnets a set of prefixes that need to be populated in the routing
667 * table of the target switch in the changedRoutes. Can be null,
668 * in which case all the configured prefixes belonging to the
669 * paired switches will be populated in the target switch
670 * @param changedRoutes a set of route-path changes, where each route-path is
671 * a list with its first element the src-switch (target)
672 * of the path, and the second element the dst-switch of
673 * the path.
674 * @return true if successful
675 */
676 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
677 Set<ArrayList<DeviceId>> changedRoutes) {
678 // aggregate route-path changes for each dst device
679 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
680 new HashMap<>();
681 for (ArrayList<DeviceId> route: changedRoutes) {
682 DeviceId dstSw = route.get(1);
683 ArrayList<ArrayList<DeviceId>> deviceRoutes =
684 routesBydevice.get(dstSw);
685 if (deviceRoutes == null) {
686 deviceRoutes = new ArrayList<>();
687 routesBydevice.put(dstSw, deviceRoutes);
688 }
689 deviceRoutes.add(route);
690 }
691 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
692 ArrayList<ArrayList<DeviceId>> deviceRoutes =
693 routesBydevice.get(impactedDstDevice);
694 for (ArrayList<DeviceId> route: deviceRoutes) {
695 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
696 route.get(0), route.get(1));
697 DeviceId targetSw = route.get(0);
698 DeviceId dstSw = route.get(1); // same as impactedDstDevice
699 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
700 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
701 nhops.put(dstSw, nextHops);
702 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
703 (subnets == null) ? Sets.newHashSet() : subnets)) {
704 return false; // abort routing and fail fast
705 }
706 log.debug("Populating flow rules from target: {} to dst: {}"
707 + " is successful", targetSw, dstSw);
708 }
709 //Only if all the flows for all impacted routes to a
710 //specific target are pushed successfully, update the
711 //ECMP graph for that target. Or else the next event
712 //would not see any changes in the ECMP graphs.
713 //In another case, the target switch has gone away, so
714 //routes can't be installed. In that case, the current map
715 //is updated here, without any flows being pushed.
716 currentEcmpSpgMap.put(impactedDstDevice,
717 updatedEcmpSpgMap.get(impactedDstDevice));
718 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
719 }
720 return true;
721 }
722
723 /**
724 * Populate ECMP rules for subnets from target to destination via nexthops.
725 *
726 * @param targetSw Device ID of target switch in which rules will be programmed
727 * @param destSw1 Device ID of final destination switch to which the rules will forward
728 * @param destSw2 Device ID of paired destination switch to which the rules will forward
729 * A null deviceId indicates packets should only be sent to destSw1
730 * @param nextHops Map indication a list of next hops per destSw
731 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
732 * @return true if it succeeds in populating rules
733 */ // refactor
734 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
735 DeviceId destSw1,
736 DeviceId destSw2,
737 Map<DeviceId, Set<DeviceId>> nextHops,
738 Set<IpPrefix> subnets) {
739 boolean result;
740 // If both target switch and dest switch are edge routers, then set IP
741 // rule for both subnet and router IP.
742 boolean targetIsEdge;
743 boolean dest1IsEdge;
744 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
745 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
746
747 try {
748 targetIsEdge = config.isEdgeDevice(targetSw);
749 dest1IsEdge = config.isEdgeDevice(destSw1);
750 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
751 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
752 if (destSw2 != null) {
753 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
754 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
755 }
756 } catch (DeviceConfigNotFoundException e) {
757 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700758 return false;
759 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700760
761 if (targetIsEdge && dest1IsEdge) {
762 subnets = (subnets != null && !subnets.isEmpty())
763 ? Sets.newHashSet(subnets)
764 : Sets.newHashSet(config.getSubnets(destSw1));
765 // XXX - Rethink this
766 /*subnets.add(dest1RouterIpv4.toIpPrefix());
767 if (dest1RouterIpv6 != null) {
768 subnets.add(dest1RouterIpv6.toIpPrefix());
769 }
770 if (destSw2 != null && dest2RouterIpv4 != null) {
771 subnets.add(dest2RouterIpv4.toIpPrefix());
772 if (dest2RouterIpv6 != null) {
773 subnets.add(dest2RouterIpv6.toIpPrefix());
774 }
775 }*/
776 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
777 + "for subnets {}", targetSw, destSw1,
778 (destSw2 != null) ? ("& " + destSw2) : "",
779 subnets);
780 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
781 destSw1, destSw2,
782 nextHops);
783 if (!result) {
784 return false;
785 }
786 /* XXX rethink this
787 IpPrefix routerIpPrefix = destRouterIpv4.toIpPrefix();
788 log.debug("* populateEcmpRoutingRulePartial in device {} towards {} "
789 + "for router IP {}", targetSw, destSw, routerIpPrefix);
790 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
791 destSw, nextHops);
792 if (!result) {
793 return false;
794 }
795 // If present we deal with IPv6 loopback.
796 if (destRouterIpv6 != null) {
797 routerIpPrefix = destRouterIpv6.toIpPrefix();
798 log.debug("* populateEcmpRoutingRulePartial in device {} towards {}"
799 + " for v6 router IP {}", targetSw, destSw, routerIpPrefix);
800 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
801 destSw, nextHops);
802 if (!result) {
803 return false;
804 }
805 }*/
Saurav Dasc88d4662017-05-15 15:34:25 -0700806 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700807
808 if (!targetIsEdge && dest1IsEdge) {
809 // MPLS rules in all non-edge target devices. These rules are for
810 // individual destinations, even if the dsts are part of edge-pairs.
811 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
812 + "all MPLS rules", targetSw, destSw1);
813 result = rulePopulator.populateMplsRule(targetSw, destSw1,
814 nextHops.get(destSw1),
815 dest1RouterIpv4);
816 if (!result) {
817 return false;
818 }
819 if (dest1RouterIpv6 != null) {
820 result = rulePopulator.populateMplsRule(targetSw, destSw1,
821 nextHops.get(destSw1),
822 dest1RouterIpv6);
823 if (!result) {
824 return false;
825 }
826 }
827 }
828
829 // To save on ECMP groups
830 // avoid MPLS rules in non-edge-devices to non-edge-devices
831 // avoid MPLS transit rules in edge-devices
832 // avoid loopback IP rules in edge-devices to non-edge-devices
833 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700834 }
835
836 /**
837 * Processes a set a route-path changes by editing hash groups.
838 *
839 * @param routeChanges a set of route-path changes, where each route-path is
840 * a list with its first element the src-switch of the path
841 * and the second element the dst-switch of the path.
842 * @param linkOrSwitchFailed true if the route changes are for a failed
843 * switch or linkDown event
844 * @param failedSwitch the switchId if the route changes are for a failed switch,
845 * otherwise null
846 */
847 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
848 boolean linkOrSwitchFailed,
849 DeviceId failedSwitch) {
850 for (ArrayList<DeviceId> route : routeChanges) {
851 DeviceId targetSw = route.get(0);
852 boolean success;
853 DeviceId dstSw = null;
854 if (route.size() > 1) {
855 dstSw = route.get(1);
856 }
857
858 if (linkOrSwitchFailed) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700859 fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700860 // it's possible that we cannot fix hash groups for a route
861 // if the target switch has failed. Nevertheless the ecmp graph
862 // for the impacted switch must still be updated.
863 if (failedSwitch != null && targetSw.equals(failedSwitch)
864 && dstSw != null) {
865 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
866 currentEcmpSpgMap.remove(targetSw);
867 log.debug("Updating ECMPspg for dst:{} removing failed "
868 + "target:{}", dstSw, targetSw);
869 return;
870 }
871 //linkfailed - update both sides
872 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das7bcbe702017-06-13 15:35:54 -0700873 if (dstSw != null) {
874 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
875 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700876 log.debug("Updating ECMPspg for dst:{} and target:{}", dstSw, targetSw);
877 } else {
878 success = fixHashGroupsForRoute(route, false);
879 if (success) {
880 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
881 if (dstSw != null) {
882 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
883 }
884 log.debug("Updating ECMPspg for target:{} and dst:{}",
885 targetSw, dstSw);
886 }
887 }
888 }
889 }
890
891 /**
892 * Edits hash groups in the src-switch (targetSw) of a route-path by
893 * calling the groupHandler to either add or remove buckets in an existing
894 * hash group.
895 *
896 * @param route a single list representing a route-path where the first element
897 * is the src-switch (targetSw) of the route-path and the
898 * second element is the dst-switch
899 * @param revoke true if buckets in the hash-groups need to be removed;
900 * false if buckets in the hash-groups need to be added
901 * @return true if the hash group editing is successful
902 */
903 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
904 boolean revoke) {
905 DeviceId targetSw = route.get(0);
906 if (route.size() < 2) {
907 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
908 return false;
909 }
910 DeviceId destSw = route.get(1);
911 log.debug("Processing fixHashGroupsForRoute: Target {} -> Dest {}",
912 targetSw, destSw);
913 boolean targetIsEdge = false;
914 try {
915 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
916 } catch (DeviceConfigNotFoundException e) {
917 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
918 + "continuing fixHash", targetSw);
919 }
920
921 // figure out the new next hops at the targetSw towards the destSw
922 Set<DeviceId> nextHops = new HashSet<>();
923 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(destSw);
924 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
925 ecmpSpg.getAllLearnedSwitchesAndVia();
926 for (Integer itrIdx : switchVia.keySet()) {
927 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
928 switchVia.get(itrIdx);
929 for (DeviceId target : swViaMap.keySet()) {
930 if (target.equals(targetSw)) {
931 // found the iteration where targetSw is reached- get nextHops
932 if (!targetIsEdge && itrIdx > 1) {
933 // optimization for spines to not use other leaves to get
934 // to a leaf to avoid loops
935 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
936 + " --> dstSw:{}", itrIdx, targetSw, destSw);
937 break;
938 }
939 for (ArrayList<DeviceId> via : swViaMap.get(target)) {
940 if (via.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700941 // the dstSw is the next-hop from the targetSw
Saurav Dasc88d4662017-05-15 15:34:25 -0700942 nextHops.add(destSw);
943 } else {
944 // first elem is next-hop in each ECMP path
945 nextHops.add(via.get(0));
946 }
947 }
948 break;
949 }
950 }
951 }
952
953 // call group handler to change hash group at targetSw
954 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
955 if (grpHandler == null) {
956 log.warn("Cannot find grouphandler for dev:{} .. aborting"
957 + " {} hash group buckets for route:{} ", targetSw,
958 (revoke) ? "revoke" : "repopulate", route);
959 return false;
960 }
961 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
962 (revoke) ? "revoke" : "repopulating",
963 targetSw, destSw, nextHops);
964 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
965 destSw, true)
966 : grpHandler.fixHashGroups(targetSw, nextHops,
967 destSw, false);
968 }
969
970 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700971 * Start the flow rule population process if it was never started. The
972 * process finishes successfully when all flow rules are set and stops with
973 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -0700974 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700975 public void startPopulationProcess() {
976 statusLock.lock();
977 try {
978 if (populationStatus == Status.IDLE
979 || populationStatus == Status.SUCCEEDED
980 || populationStatus == Status.ABORTED) {
981 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -0700982 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -0700983 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
984 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700985 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700986 } finally {
987 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700988 }
sangho20eff1d2015-04-13 15:15:58 -0700989 }
990
Saurav Dasb5c236e2016-06-07 10:08:06 -0700991 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700992 * Revoke rules of given subnet in all edge switches.
993 *
994 * @param subnets subnet being removed
995 * @return true if succeed
996 */
997 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
998 statusLock.lock();
999 try {
1000 return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
1001 } finally {
1002 statusLock.unlock();
1003 }
1004 }
1005
1006 /**
1007 * Remove ECMP graph entry for the given device. Typically called when
1008 * device is no longer available.
1009 *
1010 * @param deviceId the device for which graphs need to be purged
1011 */
1012 protected void purgeEcmpGraph(DeviceId deviceId) {
1013 currentEcmpSpgMap.remove(deviceId);
1014 if (updatedEcmpSpgMap != null) {
1015 updatedEcmpSpgMap.remove(deviceId);
1016 }
1017 }
1018
1019 //////////////////////////////////////
1020 // Routing helper methods and classes
1021 //////////////////////////////////////
1022
1023 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001024 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001025 * previous ecmp shortest-path graph exists for a switch in order to compute
1026 * affected routes. If such a graph does not exist, the method returns null.
1027 *
1028 * @param linkFail the failed link
1029 * @return the set of affected routes which may be empty if no routes were
1030 * affected, or null if no previous ecmp spg was found for comparison
1031 */
sangho20eff1d2015-04-13 15:15:58 -07001032 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
1033
1034 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1035
1036 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001037 log.debug("Computing the impacted routes for device {} due to link fail",
1038 sw.id());
Charles Chanc42e84e2015-10-20 16:24:19 -07001039 if (!srManager.mastershipService.isLocalMaster(sw.id())) {
Saurav Dasb5c236e2016-06-07 10:08:06 -07001040 log.debug("No mastership for {} .. skipping route optimization",
1041 sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001042 continue;
1043 }
Shashikanth VH013a7bc2015-12-11 01:32:44 +05301044 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001045 if (ecmpSpg == null) {
Saurav Dasb5c236e2016-06-07 10:08:06 -07001046 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1047 + " rerouting and opting for full-reroute", sw.id());
1048 return null;
sangho20eff1d2015-04-13 15:15:58 -07001049 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001050 if (log.isDebugEnabled()) {
1051 log.debug("Root switch: {}", sw.id());
1052 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1053 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(sw.id()));
1054 }
sangho20eff1d2015-04-13 15:15:58 -07001055 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1056 ecmpSpg.getAllLearnedSwitchesAndVia();
1057 for (Integer itrIdx : switchVia.keySet()) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001058 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
sangho20eff1d2015-04-13 15:15:58 -07001059 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1060 switchVia.get(itrIdx);
1061 for (DeviceId targetSw : swViaMap.keySet()) {
Saurav Das4e3224f2016-11-29 14:27:25 -08001062 DeviceId rootSw = sw.id();
Saurav Dasb5c236e2016-06-07 10:08:06 -07001063 if (log.isTraceEnabled()) {
Saurav Das4e3224f2016-11-29 14:27:25 -08001064 log.trace("TargetSwitch {} --> RootSwitch {}", targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001065 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1066 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001067 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001068 }
1069 }
sangho20eff1d2015-04-13 15:15:58 -07001070 Set<ArrayList<DeviceId>> subLinks =
Saurav Das4e3224f2016-11-29 14:27:25 -08001071 computeLinks(targetSw, rootSw, swViaMap);
sangho20eff1d2015-04-13 15:15:58 -07001072 for (ArrayList<DeviceId> alink: subLinks) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001073 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1074 alink.get(1).equals(linkFail.dst().deviceId()))
1075 ||
1076 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1077 alink.get(1).equals(linkFail.src().deviceId()))) {
Saurav Das4e3224f2016-11-29 14:27:25 -08001078 log.debug("Impacted route:{}->{}", targetSw, rootSw);
sangho20eff1d2015-04-13 15:15:58 -07001079 ArrayList<DeviceId> aRoute = new ArrayList<>();
Saurav Dasc88d4662017-05-15 15:34:25 -07001080 aRoute.add(targetSw); // switch with rules to populate
1081 aRoute.add(rootSw); // towards this destination
sangho20eff1d2015-04-13 15:15:58 -07001082 routes.add(aRoute);
1083 break;
1084 }
1085 }
1086 }
1087 }
sangho45b009c2015-05-07 13:30:57 -07001088
sangho20eff1d2015-04-13 15:15:58 -07001089 }
1090
1091 return routes;
1092 }
1093
Saurav Das4e3224f2016-11-29 14:27:25 -08001094 /**
1095 * Computes set of affected routes due to new links or failed switches.
1096 *
1097 * @return the set of affected routes which may be empty if no routes were
1098 * affected
1099 */
sangho20eff1d2015-04-13 15:15:58 -07001100 private Set<ArrayList<DeviceId>> computeRouteChange() {
Saurav Das7bcbe702017-06-13 15:35:54 -07001101 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001102 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001103
1104 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001105 log.debug("Computing the impacted routes for device {}", sw.id());
1106 DeviceId retId = shouldHandleRouting(sw.id());
1107 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001108 continue;
1109 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001110 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1111 for (DeviceId rootSw : devicesToProcess) {
1112 if (log.isTraceEnabled()) {
1113 log.trace("Device links for dev: {}", rootSw);
1114 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1115 log.trace("{} -> {} ", link.src().deviceId(),
1116 link.dst().deviceId());
1117 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001118 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001119 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1120 if (currEcmpSpg == null) {
1121 log.debug("No existing ECMP graph for device {}.. adding self as "
1122 + "changed route", rootSw);
1123 changedRtBldr.add(Lists.newArrayList(rootSw));
1124 continue;
1125 }
1126 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
1127 if (log.isDebugEnabled()) {
1128 log.debug("Root switch: {}", rootSw);
1129 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1130 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1131 }
1132 // first use the updated/new map to compare to current/existing map
1133 // as new links may have come up
1134 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1135 // then use the current/existing map to compare to updated/new map
1136 // as switch may have been removed
1137 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001138 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001139 }
sangho20eff1d2015-04-13 15:15:58 -07001140
Saurav Das7bcbe702017-06-13 15:35:54 -07001141 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001142 for (ArrayList<DeviceId> route: changedRoutes) {
1143 log.debug("Route changes Target -> Root");
1144 if (route.size() == 1) {
1145 log.debug(" : all -> {}", route.get(0));
1146 } else {
1147 log.debug(" : {} -> {}", route.get(0), route.get(1));
1148 }
1149 }
1150 return changedRoutes;
1151 }
1152
1153 /**
1154 * For the root switch, searches all the target nodes reachable in the base
1155 * graph, and compares paths to the ones in the comp graph.
1156 *
1157 * @param base the graph that is indexed for all reachable target nodes
1158 * from the root node
1159 * @param comp the graph that the base graph is compared to
1160 * @param rootSw both ecmp graphs are calculated for the root node
1161 * @return all the routes that have changed in the base graph
1162 */
1163 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1164 EcmpShortestPathGraph comp,
1165 DeviceId rootSw) {
1166 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1167 ImmutableSet.builder();
1168 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1169 base.getAllLearnedSwitchesAndVia();
1170 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1171 comp.getAllLearnedSwitchesAndVia();
1172 for (Integer itrIdx : baseMap.keySet()) {
1173 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1174 baseMap.get(itrIdx);
1175 for (DeviceId targetSw : baseViaMap.keySet()) {
1176 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1177 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1178 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001179 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001180 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001181 route.add(targetSw); // switch with rules to populate
1182 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001183 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001184 }
1185 }
sangho45b009c2015-05-07 13:30:57 -07001186 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001187 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001188 }
1189
Saurav Das7bcbe702017-06-13 15:35:54 -07001190 /**
1191 * Returns the ECMP paths traversed to reach the target switch.
1192 *
1193 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1194 * @param targetSw the switch to reach from the root switch
1195 * @return the nodes traversed on ECMP paths to the target switch
1196 */
sangho20eff1d2015-04-13 15:15:58 -07001197 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001198 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001199 for (Integer itrIdx : switchVia.keySet()) {
1200 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1201 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001202 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001203 continue;
1204 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001205 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001206 }
1207 }
1208
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001209 return null;
sangho20eff1d2015-04-13 15:15:58 -07001210 }
1211
Saurav Das7bcbe702017-06-13 15:35:54 -07001212 /**
1213 * Utility method to break down a path from src to dst device into a collection
1214 * of links.
1215 *
1216 * @param src src device of the path
1217 * @param dst dst device of the path
1218 * @param viaMap path taken from src to dst device
1219 * @return collection of links in the path
1220 */
sangho20eff1d2015-04-13 15:15:58 -07001221 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1222 DeviceId dst,
1223 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1224 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1225 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1226 DeviceId linkSrc = src;
1227 DeviceId linkDst = dst;
1228 for (DeviceId viaDevice: via) {
1229 ArrayList<DeviceId> link = new ArrayList<>();
1230 linkDst = viaDevice;
1231 link.add(linkSrc);
1232 link.add(linkDst);
1233 subLinks.add(link);
1234 linkSrc = viaDevice;
1235 }
1236 ArrayList<DeviceId> link = new ArrayList<>();
1237 link.add(linkSrc);
1238 link.add(dst);
1239 subLinks.add(link);
1240 }
1241
1242 return subLinks;
1243 }
1244
Charles Chan93e71ba2016-04-29 14:38:22 -07001245 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001246 * Determines whether this controller instance should handle routing for the
1247 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
1248 * Returns null if this instance should not handle routing for given {@code deviceId}.
1249 * Otherwise the returned value could be the given deviceId itself, or the
1250 * deviceId for the paired edge device. In the latter case, this instance
1251 * should handle routing for both the given device and the paired device.
Charles Chan93e71ba2016-04-29 14:38:22 -07001252 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001253 * @param deviceId device identifier to consider for routing
1254 * @return null or deviceId which could be the same as the given deviceId
1255 * or the deviceId of a paired edge device
Charles Chan93e71ba2016-04-29 14:38:22 -07001256 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001257 private DeviceId shouldHandleRouting(DeviceId deviceId) {
1258 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1259 log.debug("Not master for dev:{} .. skipping routing, may get handled "
1260 + "elsewhere as part of paired devices", deviceId);
1261 return null;
1262 }
1263 NodeId myNode = srManager.mastershipService.getMasterFor(deviceId);
1264 DeviceId pairDev = getPairDev(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001265
Saurav Das7bcbe702017-06-13 15:35:54 -07001266 if (pairDev != null) {
1267 if (!srManager.deviceService.isAvailable(pairDev)) {
1268 log.warn("pairedDev {} not available .. routing this dev:{} "
1269 + "without mastership check",
1270 pairDev, deviceId);
1271 return pairDev; // handle both temporarily
1272 }
1273 NodeId pairMasterNode = srManager.mastershipService.getMasterFor(pairDev);
1274 if (myNode.compareTo(pairMasterNode) <= 0) {
1275 log.debug("Handling routing for both dev:{} pair-dev:{}; myNode: {}"
1276 + " pairMaster:{} compare:{}", deviceId, pairDev,
1277 myNode, pairMasterNode,
1278 myNode.compareTo(pairMasterNode));
1279 return pairDev; // handle both
1280 } else {
1281 log.debug("PairDev node: {} should handle routing for dev:{} and "
1282 + "pair-dev:{}", pairMasterNode, deviceId, pairDev);
1283 return null; // handle neither
sanghob35a6192015-04-01 13:05:26 -07001284 }
1285 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001286 return deviceId; // not paired, just handle given device
sanghob35a6192015-04-01 13:05:26 -07001287 }
1288
Charles Chan93e71ba2016-04-29 14:38:22 -07001289 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001290 * Returns the configured paired DeviceId for the given Device, or null
1291 * if no such paired device has been configured.
Charles Chan93e71ba2016-04-29 14:38:22 -07001292 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001293 * @param deviceId
1294 * @return configured pair deviceId or null
Charles Chan93e71ba2016-04-29 14:38:22 -07001295 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001296 private DeviceId getPairDev(DeviceId deviceId) {
1297 DeviceId pairDev;
Charles Chan0b4e6182015-11-03 10:42:14 -08001298 try {
Saurav Das7bcbe702017-06-13 15:35:54 -07001299 pairDev = srManager.deviceConfiguration.getPairDeviceId(deviceId);
Charles Chan0b4e6182015-11-03 10:42:14 -08001300 } catch (DeviceConfigNotFoundException e) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001301 log.warn(e.getMessage() + " .. cannot continue routing for dev: {}");
1302 return null;
Charles Chan0b4e6182015-11-03 10:42:14 -08001303 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001304 return pairDev;
sanghob35a6192015-04-01 13:05:26 -07001305 }
1306
1307 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001308 * Returns the set of deviceIds which are the next hops from the targetSw
1309 * to the dstSw according to the latest ECMP spg.
1310 *
1311 * @param targetSw the switch for which the next-hops are desired
1312 * @param dstSw the switch to which the next-hops lead to from the targetSw
1313 * @return set of next hop deviceIds, could be empty if no next hops are found
1314 */
1315 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1316 boolean targetIsEdge = false;
1317 try {
1318 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1319 } catch (DeviceConfigNotFoundException e) {
1320 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1321 + "continuing to getNextHops", targetSw);
1322 }
1323
1324 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1325 if (ecmpSpg == null) {
1326 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1327 return ImmutableSet.of();
1328 }
1329 HashMap<Integer,
1330 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1331 ecmpSpg.getAllLearnedSwitchesAndVia();
1332 for (Integer itrIdx : switchVia.keySet()) {
1333 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1334 switchVia.get(itrIdx);
1335 for (DeviceId target : swViaMap.keySet()) {
1336 if (!target.equals(targetSw)) {
1337 continue;
1338 }
1339 if (!targetIsEdge && itrIdx > 1) {
1340 // optimization for spines to not use other leaves to get
1341 // to a leaf to avoid loops
1342 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1343 + " --> dstSw:{}", itrIdx, targetSw, dstSw);
1344 break;
1345 }
1346 Set<DeviceId> nextHops = new HashSet<>();
1347 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1348 if (via.isEmpty()) {
1349 // the dstSw is the next-hop from the targetSw
1350 nextHops.add(dstSw);
1351 } else {
1352 // first elem is next-hop in each ECMP path
1353 nextHops.add(via.get(0));
1354 }
1355 }
1356 return nextHops;
1357 }
1358 }
1359 return ImmutableSet.of(); //no next-hops found
1360 }
1361
1362 /**
1363 * Represents two devices that are paired by configuration. An EdgePair for
1364 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1365 */
1366 protected final class EdgePair {
1367 DeviceId dev1;
1368 DeviceId dev2;
1369
1370 EdgePair(DeviceId dev1, DeviceId dev2) {
1371 this.dev1 = dev1;
1372 this.dev2 = dev2;
1373 }
1374
1375 boolean includes(DeviceId dev) {
1376 return dev1.equals(dev) || dev2.equals(dev);
1377 }
1378
1379 @Override
1380 public boolean equals(Object o) {
1381 if (this == o) {
1382 return true;
1383 }
1384 if (!(o instanceof EdgePair)) {
1385 return false;
1386 }
1387 EdgePair that = (EdgePair) o;
1388 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1389 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1390 }
1391
1392 @Override
1393 public int hashCode() {
1394 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1395 return Objects.hash(dev1, dev2);
1396 } else {
1397 return Objects.hash(dev2, dev1);
1398 }
1399 }
1400
1401 @Override
1402 public String toString() {
1403 return toStringHelper(this)
1404 .add("Dev1", dev1)
1405 .add("Dev2", dev2)
1406 .toString();
1407 }
1408 }
1409
1410 //////////////////////////////////////
1411 // Filtering rule creation
1412 //////////////////////////////////////
1413
1414 /**
Saurav Das018605f2017-02-18 14:05:44 -08001415 * Populates filtering rules for port, and punting rules
1416 * for gateway IPs, loopback IPs and arp/ndp traffic.
1417 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001418 *
1419 * @param deviceId Switch ID to set the rules
1420 */
Saurav Das822c4e22015-10-23 10:51:11 -07001421 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001422 // Although device is added, sometimes device store does not have the
1423 // ports for this device yet. It results in missing filtering rules in the
1424 // switch. We will attempt it a few times. If it still does not work,
1425 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001426 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001427 if (firstRun == null) {
1428 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001429 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001430 executorService.schedule(new RetryFilters(deviceId, firstRun),
1431 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001432 }
1433
1434 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001435 * Utility class used to temporarily store information about the ports on a
1436 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001437 */
1438 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001439 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001440
Saurav Das018605f2017-02-18 14:05:44 -08001441 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001442 int filteredPorts) {
1443 this.disabledPorts = disabledPorts;
1444 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001445 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001446 }
1447
1448 @Override
1449 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001450 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001451 }
1452
1453 @Override
1454 public boolean equals(Object obj) {
1455 if (this == obj) {
1456 return true;
1457 }
1458 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1459 return false;
1460 }
1461 PortFilterInfo other = (PortFilterInfo) obj;
1462 return ((disabledPorts == other.disabledPorts) &&
1463 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001464 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001465 }
1466
1467 @Override
1468 public String toString() {
1469 MoreObjects.ToStringHelper helper = toStringHelper(this)
1470 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001471 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001472 .add("filteredPorts", filteredPorts);
1473 return helper.toString();
1474 }
1475 }
1476
1477 /**
1478 * RetryFilters populates filtering objectives for a device and keeps retrying
1479 * till the number of ports filtered are constant for a predefined number
1480 * of attempts.
1481 */
1482 protected final class RetryFilters implements Runnable {
1483 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1484 DeviceId devId;
1485 int counter;
1486 PortFilterInfo prevRun;
1487
1488 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001489 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001490 prevRun = previousRun;
1491 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001492 }
1493
1494 @Override
1495 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001496 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001497 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001498 boolean sameResult = prevRun.equals(thisRun);
1499 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1500 thisRun, sameResult);
1501 if (thisRun == null || !sameResult || (sameResult && --constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001502 // exponentially increasing intervals for retries
1503 executorService.schedule(this,
1504 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1505 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001506 if (!sameResult) {
1507 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1508 }
Saurav Das59232cf2016-04-27 18:35:50 -07001509 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001510 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001511 }
Saurav Das59232cf2016-04-27 18:35:50 -07001512 }
1513
sanghob35a6192015-04-01 13:05:26 -07001514}