blob: bcd6a750f399c6d58979501533be8e4bb9fe04bf [file] [log] [blame]
sangho80f11cb2015-04-01 13:05:26 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sangho80f11cb2015-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 Dasd1872b02016-12-02 15:43:47 -080018import com.google.common.base.MoreObjects;
Saurav Das62ae6792017-05-15 15:34:25 -070019import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableMap.Builder;
Charles Chanc22cef32016-04-29 14:38:22 -070021import com.google.common.collect.ImmutableSet;
Saurav Das1b391d52016-11-29 14:27:25 -080022import com.google.common.collect.Lists;
sanghofb7c7292015-04-13 15:15:58 -070023import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
sangho9b169e32015-04-14 16:27:13 -070025import org.onlab.packet.Ip4Address;
Pier Ventreadb4ae62016-11-23 09:57:42 -080026import org.onlab.packet.Ip6Address;
sangho80f11cb2015-04-01 13:05:26 -070027import org.onlab.packet.IpPrefix;
Saurav Das261c3002017-06-13 15:35:54 -070028import org.onosproject.cluster.NodeId;
Charles Chanc22cef32016-04-29 14:38:22 -070029import org.onosproject.net.ConnectPoint;
sangho80f11cb2015-04-01 13:05:26 -070030import org.onosproject.net.Device;
31import org.onosproject.net.DeviceId;
sanghofb7c7292015-04-13 15:15:58 -070032import org.onosproject.net.Link;
Charles Chan319d1a22015-11-03 10:42:14 -080033import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
34import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Das62ae6792017-05-15 15:34:25 -070035import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
sangho80f11cb2015-04-01 13:05:26 -070036import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.HashSet;
Saurav Das261c3002017-06-13 15:35:54 -070042import java.util.Iterator;
43import java.util.Map;
Saurav Dasd1872b02016-12-02 15:43:47 -080044import java.util.Objects;
sangho80f11cb2015-04-01 13:05:26 -070045import java.util.Set;
Saurav Das07c74602016-04-27 18:35:50 -070046import java.util.concurrent.ScheduledExecutorService;
47import java.util.concurrent.TimeUnit;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090048import java.util.concurrent.locks.Lock;
49import java.util.concurrent.locks.ReentrantLock;
sangho80f11cb2015-04-01 13:05:26 -070050
Saurav Dasd1872b02016-12-02 15:43:47 -080051import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventreadb4ae62016-11-23 09:57:42 -080052import static com.google.common.base.Preconditions.checkNotNull;
53import static java.util.concurrent.Executors.newScheduledThreadPool;
54import static org.onlab.util.Tools.groupedThreads;
sangho80f11cb2015-04-01 13:05:26 -070055
Charles Chanb7f75ac2016-01-11 18:28:54 -080056/**
57 * Default routing handler that is responsible for route computing and
58 * routing rule population.
59 */
sangho80f11cb2015-04-01 13:05:26 -070060public class DefaultRoutingHandler {
Saurav Dasf9332192017-02-18 14:05:44 -080061 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
62 private static final int RETRY_INTERVAL_MS = 250;
63 private static final int RETRY_INTERVAL_SCALE = 1;
Charles Chanc22cef32016-04-29 14:38:22 -070064 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sangho80f11cb2015-04-01 13:05:26 -070065
66 private SegmentRoutingManager srManager;
67 private RoutingRulePopulator rulePopulator;
Shashikanth VH0637b162015-12-11 01:32:44 +053068 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
69 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho9b169e32015-04-14 16:27:13 -070070 private DeviceConfiguration config;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090071 private final Lock statusLock = new ReentrantLock();
72 private volatile Status populationStatus;
Yuta HIGUCHIebee2f12016-07-21 16:54:33 -070073 private ScheduledExecutorService executorService
Saurav Dasd1872b02016-12-02 15:43:47 -080074 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
sangho80f11cb2015-04-01 13:05:26 -070075
76 /**
77 * Represents the default routing population status.
78 */
79 public enum Status {
80 // population process is not started yet.
81 IDLE,
82
83 // population process started.
84 STARTED,
85
Srikanth Vavilapalli64505482015-04-21 13:04:13 -070086 // population process was aborted due to errors, mostly for groups not
87 // found.
sangho80f11cb2015-04-01 13:05:26 -070088 ABORTED,
89
90 // population process was finished successfully.
91 SUCCEEDED
92 }
93
94 /**
95 * Creates a DefaultRoutingHandler object.
96 *
97 * @param srManager SegmentRoutingManager object
98 */
99 public DefaultRoutingHandler(SegmentRoutingManager srManager) {
100 this.srManager = srManager;
101 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho9b169e32015-04-14 16:27:13 -0700102 this.config = checkNotNull(srManager.deviceConfiguration);
sangho80f11cb2015-04-01 13:05:26 -0700103 this.populationStatus = Status.IDLE;
sanghofb7c7292015-04-13 15:15:58 -0700104 this.currentEcmpSpgMap = Maps.newHashMap();
sangho80f11cb2015-04-01 13:05:26 -0700105 }
106
107 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700108 * Returns an immutable copy of the current ECMP shortest-path graph as
109 * computed by this controller instance.
110 *
Saurav Das261c3002017-06-13 15:35:54 -0700111 * @return immutable copy of the current ECMP graph
Saurav Das62ae6792017-05-15 15:34:25 -0700112 */
113 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
114 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
115 currentEcmpSpgMap.entrySet().forEach(entry -> {
116 if (entry.getValue() != null) {
117 builder.put(entry.getKey(), entry.getValue());
118 }
119 });
120 return builder.build();
121 }
122
Saurav Das261c3002017-06-13 15:35:54 -0700123 //////////////////////////////////////
124 // Route path handling
125 //////////////////////////////////////
126
127 /* The following three methods represent the three major ways in routing
128 * is triggered in the network
129 * a) due to configuration change
130 * b) due to route-added event
131 * c) due to change in the topology
132 */
133
Saurav Das62ae6792017-05-15 15:34:25 -0700134 /**
Saurav Das261c3002017-06-13 15:35:54 -0700135 * Populates all routing rules to all switches. Typically triggered at
136 * startup or after a configuration event.
sangho80f11cb2015-04-01 13:05:26 -0700137 */
Saurav Das62ae6792017-05-15 15:34:25 -0700138 public void populateAllRoutingRules() {
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900139 statusLock.lock();
140 try {
Saurav Das261c3002017-06-13 15:35:54 -0700141 if (populationStatus == Status.STARTED) {
142 log.warn("Previous rule population is not finished. Cannot"
143 + " proceed with populateAllRoutingRules");
144 return;
145 }
146
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900147 populationStatus = Status.STARTED;
148 rulePopulator.resetCounter();
Saurav Das261c3002017-06-13 15:35:54 -0700149 log.info("Starting to populate all routing rules");
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900150 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sangho80f11cb2015-04-01 13:05:26 -0700151
Saurav Das261c3002017-06-13 15:35:54 -0700152 // take a snapshot of the topology
153 updatedEcmpSpgMap = new HashMap<>();
154 Set<EdgePair> edgePairs = new HashSet<>();
155 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
156 for (Device dstSw : srManager.deviceService.getDevices()) {
157 EcmpShortestPathGraph ecmpSpgUpdated =
158 new EcmpShortestPathGraph(dstSw.id(), srManager);
159 updatedEcmpSpgMap.put(dstSw.id(), ecmpSpgUpdated);
160 DeviceId pairDev = getPairDev(dstSw.id());
161 if (pairDev != null) {
162 // pairDev may not be available yet, but we still need to add
163 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
164 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
165 edgePairs.add(new EdgePair(dstSw.id(), pairDev));
166 }
167 DeviceId ret = shouldHandleRouting(dstSw.id());
168 if (ret == null) {
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900169 continue;
170 }
Saurav Das261c3002017-06-13 15:35:54 -0700171 Set<DeviceId> devsToProcess = Sets.newHashSet(dstSw.id(), ret);
172 // To do a full reroute, assume all routes have changed
173 for (DeviceId dev : devsToProcess) {
174 for (Device targetSw : srManager.deviceService.getDevices()) {
175 if (targetSw.id().equals(dev)) {
176 continue;
177 }
178 routeChanges.add(Lists.newArrayList(targetSw.id(), dev));
179 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900180 }
Saurav Das261c3002017-06-13 15:35:54 -0700181 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900182
Saurav Das261c3002017-06-13 15:35:54 -0700183 if (!redoRouting(routeChanges, edgePairs, null)) {
184 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
185 populationStatus = Status.ABORTED;
186 log.warn("Failed to repopulate all routing rules.");
187 return;
sangho80f11cb2015-04-01 13:05:26 -0700188 }
189
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900190 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
191 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700192 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900193 rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700194 return;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900195 } finally {
196 statusLock.unlock();
sangho80f11cb2015-04-01 13:05:26 -0700197 }
sangho80f11cb2015-04-01 13:05:26 -0700198 }
199
sanghofb7c7292015-04-13 15:15:58 -0700200 /**
Saurav Das261c3002017-06-13 15:35:54 -0700201 * Populate rules from all other edge devices to the connect-point(s)
202 * specified for the given subnets.
203 *
204 * @param cpts connect point(s) of the subnets being added
205 * @param subnets subnets being added
206 */ //XXX refactor
207 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
208 statusLock.lock();
209 try {
210 if (populationStatus == Status.STARTED) {
211 log.warn("Previous rule population is not finished. Cannot"
212 + " proceed with routing rules for added routes");
213 return;
214 }
215 populationStatus = Status.STARTED;
216 rulePopulator.resetCounter();
217 log.info("Starting to populate routing rules for added routes");
218 // Take snapshots of the topology
219 updatedEcmpSpgMap = new HashMap<>();
220 Set<EdgePair> edgePairs = new HashSet<>();
221 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
222 boolean handleRouting = false;
223
224 if (cpts.size() == 2) {
225 // ensure connect points are edge-pairs
226 Iterator<ConnectPoint> iter = cpts.iterator();
227 DeviceId dev1 = iter.next().deviceId();
228 DeviceId pairDev = getPairDev(dev1);
229 if (iter.next().deviceId().equals(pairDev)) {
230 edgePairs.add(new EdgePair(dev1, pairDev));
231 } else {
232 log.warn("Connectpoints {} for subnets {} not on "
233 + "pair-devices.. aborting populateSubnet", cpts, subnets);
234 populationStatus = Status.ABORTED;
235 return;
236 }
237 for (ConnectPoint cp : cpts) {
238 EcmpShortestPathGraph ecmpSpgUpdated =
239 new EcmpShortestPathGraph(cp.deviceId(), srManager);
240 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
241 DeviceId retId = shouldHandleRouting(cp.deviceId());
242 if (retId == null) {
243 continue;
244 }
245 handleRouting = true;
246 }
247 } else {
248 // single connect point
249 DeviceId dstSw = cpts.iterator().next().deviceId();
250 EcmpShortestPathGraph ecmpSpgUpdated =
251 new EcmpShortestPathGraph(dstSw, srManager);
252 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
253 if (srManager.mastershipService.isLocalMaster(dstSw)) {
254 handleRouting = true;
255 }
256 }
257
258 if (!handleRouting) {
259 log.debug("This instance is not handling ecmp routing to the "
260 + "connectPoint(s) {}", cpts);
261 populationStatus = Status.ABORTED;
262 return;
263 }
264
265 // if it gets here, this instance should handle routing for the
266 // connectpoint(s). Assume all route-paths have to be updated to
267 // the connectpoint(s) with the following exceptions
268 // 1. if target is non-edge no need for routing rules
269 // 2. if target is one of the connectpoints
270 for (ConnectPoint cp : cpts) {
271 DeviceId dstSw = cp.deviceId();
272 for (Device targetSw : srManager.deviceService.getDevices()) {
273 boolean isEdge = false;
274 try {
275 isEdge = config.isEdgeDevice(targetSw.id());
276 } catch (DeviceConfigNotFoundException e) {
277 log.warn(e.getMessage() + "aborting populateSubnet");
278 populationStatus = Status.ABORTED;
279 return;
280 }
281 if (dstSw.equals(targetSw.id()) || !isEdge ||
282 (cpts.size() == 2 &&
283 targetSw.id().equals(getPairDev(dstSw)))) {
284 continue;
285 }
286 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
287 }
288 }
289
290 if (!redoRouting(routeChanges, edgePairs, subnets)) {
291 log.debug("populateSubnet: populationStatus is ABORTED");
292 populationStatus = Status.ABORTED;
293 log.warn("Failed to repopulate the rules for subnet.");
294 return;
295 }
296
297 log.debug("populateSubnet: populationStatus is SUCCEEDED");
298 populationStatus = Status.SUCCEEDED;
299 log.info("Completed subnet population. Total # of rules pushed : {}",
300 rulePopulator.getCounter());
301 return;
302
303 } finally {
304 statusLock.unlock();
305 }
306 }
307
308 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700309 * Populates the routing rules or makes hash group changes according to the
310 * route-path changes due to link failure, switch failure or link up. This
311 * method should only be called for one of these three possible event-types.
312 * Note that when a switch goes away, all of its links fail as well,
313 * but this is handled as a single switch removal event.
sanghofb7c7292015-04-13 15:15:58 -0700314 *
Saurav Das62ae6792017-05-15 15:34:25 -0700315 * @param linkDown the single failed link, or null for other conditions
316 * such as link-up or a removed switch
317 * @param linkUp the single link up, or null for other conditions such as
318 * link-down or a removed switch
319 * @param switchDown the removed switch, or null for other conditions such as
320 * link-down or link-up
Saurav Das261c3002017-06-13 15:35:54 -0700321 */ // refactor
Saurav Das62ae6792017-05-15 15:34:25 -0700322 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
323 Link linkUp,
324 DeviceId switchDown) {
325 if ((linkDown != null && (linkUp != null || switchDown != null)) ||
326 (linkUp != null && (linkDown != null || switchDown != null)) ||
327 (switchDown != null && (linkUp != null || linkDown != null))) {
328 log.warn("Only one event can be handled for link status change .. aborting");
329 return;
330 }
sanghofb7c7292015-04-13 15:15:58 -0700331
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900332 statusLock.lock();
333 try {
sanghofb7c7292015-04-13 15:15:58 -0700334
335 if (populationStatus == Status.STARTED) {
Saurav Das261c3002017-06-13 15:35:54 -0700336 log.warn("Previous rule population is not finished. Cannot"
337 + " proceeed with routingRules for Link Status change");
Saurav Das62ae6792017-05-15 15:34:25 -0700338 return;
sanghofb7c7292015-04-13 15:15:58 -0700339 }
340
Saurav Das261c3002017-06-13 15:35:54 -0700341 // Take snapshots of the topology
sangho28d0b6d2015-05-07 13:30:57 -0700342 updatedEcmpSpgMap = new HashMap<>();
Saurav Das261c3002017-06-13 15:35:54 -0700343 Set<EdgePair> edgePairs = new HashSet<>();
sangho28d0b6d2015-05-07 13:30:57 -0700344 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH0637b162015-12-11 01:32:44 +0530345 EcmpShortestPathGraph ecmpSpgUpdated =
346 new EcmpShortestPathGraph(sw.id(), srManager);
sangho28d0b6d2015-05-07 13:30:57 -0700347 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Saurav Das261c3002017-06-13 15:35:54 -0700348 DeviceId pairDev = getPairDev(sw.id());
349 if (pairDev != null) {
350 // pairDev may not be available yet, but we still need to add
351 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
352 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
353 edgePairs.add(new EdgePair(sw.id(), pairDev));
354 }
sangho28d0b6d2015-05-07 13:30:57 -0700355 }
356
Saurav Das261c3002017-06-13 15:35:54 -0700357 log.info("Starting to populate routing rules from link status change");
sanghodf0153f2015-05-05 14:13:34 -0700358
sanghofb7c7292015-04-13 15:15:58 -0700359 Set<ArrayList<DeviceId>> routeChanges;
Saurav Das62ae6792017-05-15 15:34:25 -0700360 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700361 + "populationStatus is STARTED");
sanghofb7c7292015-04-13 15:15:58 -0700362 populationStatus = Status.STARTED;
Saurav Das261c3002017-06-13 15:35:54 -0700363 rulePopulator.resetCounter();
Saurav Das1b391d52016-11-29 14:27:25 -0800364 // try optimized re-routing
Saurav Das62ae6792017-05-15 15:34:25 -0700365 if (linkDown == null) {
366 // either a linkUp or a switchDown - compute all route changes by
367 // comparing all routes of existing ECMP SPG to new ECMP SPG
sanghofb7c7292015-04-13 15:15:58 -0700368 routeChanges = computeRouteChange();
Saurav Das62ae6792017-05-15 15:34:25 -0700369
370 if (routeChanges != null) {
371 // deal with linkUp of a seen-before link
372 if (linkUp != null && srManager.isSeenLink(linkUp)) {
Saurav Das261c3002017-06-13 15:35:54 -0700373 if (!srManager.isBidirectional(linkUp)) {
Saurav Das62ae6792017-05-15 15:34:25 -0700374 log.warn("Not a bidirectional link yet .. not "
375 + "processing link {}", linkUp);
376 srManager.updateSeenLink(linkUp, true);
377 populationStatus = Status.ABORTED;
378 return;
379 }
380 // link previously seen before
381 // do hash-bucket changes instead of a re-route
382 processHashGroupChange(routeChanges, false, null);
383 // clear out routesChanges so a re-route is not attempted
384 routeChanges = ImmutableSet.of();
385 }
386
387 //deal with switchDown
388 if (switchDown != null) {
389 processHashGroupChange(routeChanges, true, switchDown);
390 // clear out routesChanges so a re-route is not attempted
391 routeChanges = ImmutableSet.of();
392 }
393
394 // for a linkUp of a never-seen-before link
395 // let it fall through to a reroute of the routeChanges
396
397 }
398
399 // now that we are past the check for a previously seen link
400 // it is safe to update the store for the linkUp
401 if (linkUp != null) {
402 srManager.updateSeenLink(linkUp, true);
403 }
404
sanghofb7c7292015-04-13 15:15:58 -0700405 } else {
Saurav Das62ae6792017-05-15 15:34:25 -0700406 // link has gone down
407 // Compare existing ECMP SPG only with the link that went down
408 routeChanges = computeDamagedRoutes(linkDown);
409 if (routeChanges != null) {
410 processHashGroupChange(routeChanges, true, null);
411 // clear out routesChanges so a re-route is not attempted
412 routeChanges = ImmutableSet.of();
413 }
sanghofb7c7292015-04-13 15:15:58 -0700414 }
415
Saurav Das1b391d52016-11-29 14:27:25 -0800416 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb149be12016-06-07 10:08:06 -0700417 if (routeChanges == null) {
Saurav Das261c3002017-06-13 15:35:54 -0700418 log.info("Optimized routing failed... opting for full reroute");
419 populationStatus = Status.ABORTED;
420 statusLock.unlock();
Saurav Das62ae6792017-05-15 15:34:25 -0700421 populateAllRoutingRules();
422 return;
Saurav Dasb149be12016-06-07 10:08:06 -0700423 }
424
sanghofb7c7292015-04-13 15:15:58 -0700425 if (routeChanges.isEmpty()) {
Saurav Das62ae6792017-05-15 15:34:25 -0700426 log.info("No re-route attempted for the link status change");
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700427 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700428 populationStatus = Status.SUCCEEDED;
Saurav Das62ae6792017-05-15 15:34:25 -0700429 return;
sanghofb7c7292015-04-13 15:15:58 -0700430 }
431
Saurav Das62ae6792017-05-15 15:34:25 -0700432 // reroute of routeChanges
Saurav Das261c3002017-06-13 15:35:54 -0700433 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700434 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700435 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700436 log.info("Completed repopulation of rules for link-status change."
437 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700438 return;
sanghofb7c7292015-04-13 15:15:58 -0700439 } else {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700440 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sanghofb7c7292015-04-13 15:15:58 -0700441 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700442 log.warn("Failed to repopulate the rules for link status change.");
Saurav Das62ae6792017-05-15 15:34:25 -0700443 return;
sanghofb7c7292015-04-13 15:15:58 -0700444 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900445 } finally {
446 statusLock.unlock();
sanghofb7c7292015-04-13 15:15:58 -0700447 }
448 }
449
Saurav Das62ae6792017-05-15 15:34:25 -0700450 /**
Saurav Das261c3002017-06-13 15:35:54 -0700451 * Processes a set a route-path changes by reprogramming routing rules and
452 * creating new hash-groups or editing them if necessary. This method also
453 * determines the next-hops for the route-path from the src-switch (target)
454 * of the path towards the dst-switch of the path.
Saurav Das62ae6792017-05-15 15:34:25 -0700455 *
Saurav Das261c3002017-06-13 15:35:54 -0700456 * @param routeChanges a set of route-path changes, where each route-path is
457 * a list with its first element the src-switch (target)
458 * of the path, and the second element the dst-switch of
459 * the path.
460 * @param edgePairs a set of edge-switches that are paired by configuration
461 * @param subnets a set of prefixes that need to be populated in the routing
462 * table of the target switch in the route-path. Can be null,
463 * in which case all the prefixes belonging to the dst-switch
464 * will be populated in the target switch
465 * @return true if successful in repopulating all routes
Saurav Das62ae6792017-05-15 15:34:25 -0700466 */
Saurav Das261c3002017-06-13 15:35:54 -0700467 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
468 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
469 // first make every entry two-elements
470 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
471 for (ArrayList<DeviceId> route : routeChanges) {
472 if (route.size() == 1) {
473 DeviceId dstSw = route.get(0);
474 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
475 if (ec == null) {
476 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
477 return false;
478 }
479 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
480 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
481 changedRoutes.add(Lists.newArrayList(target, dstSw));
482 });
483 });
484 } else {
485 DeviceId targetSw = route.get(0);
486 DeviceId dstSw = route.get(1);
487 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
488 }
489 }
490
491 // now process changedRoutes according to edgePairs
492 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
493 return false; //abort routing and fail fast
494 }
495
496 // whatever is left in changedRoutes is now processed for individual dsts.
497 if (!redoRoutingIndividualDests(subnets, changedRoutes)) {
498 return false; //abort routing and fail fast
499 }
500
501 //XXX should we do hashgroupchanges here?
502
503 // update ecmpSPG for all edge-pairs
504 for (EdgePair ep : edgePairs) {
505 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
506 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
507 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
508 }
509 return true;
510 }
511
512 /**
513 * Programs targetSw in the changedRoutes for given prefixes reachable by
514 * an edgePair. If no prefixes are given, the method will use configured
515 * subnets/prefixes. If some configured subnets belong only to a specific
516 * destination in the edgePair, then the target switch will be programmed
517 * only to that destination.
518 *
519 * @param edgePairs set of edge-pairs for which target will be programmed
520 * @param subnets a set of prefixes that need to be populated in the routing
521 * table of the target switch in the changedRoutes. Can be null,
522 * in which case all the configured prefixes belonging to the
523 * paired switches will be populated in the target switch
524 * @param changedRoutes a set of route-path changes, where each route-path is
525 * a list with its first element the src-switch (target)
526 * of the path, and the second element the dst-switch of
527 * the path.
528 * @return true if successful
529 */
530 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
531 Set<IpPrefix> subnets,
532 Set<ArrayList<DeviceId>> changedRoutes) {
533 for (EdgePair ep : edgePairs) {
534 // temp store for a target's changedRoutes to this edge-pair
535 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
536 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
537 while (i.hasNext()) {
538 ArrayList<DeviceId> route = i.next();
539 DeviceId dstSw = route.get(1);
540 if (ep.includes(dstSw)) {
541 // routeChange for edge pair found
542 // sort by target iff target is edge and remove from changedRoutes
543 DeviceId targetSw = route.get(0);
544 try {
545 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
546 continue;
547 }
548 } catch (DeviceConfigNotFoundException e) {
549 log.warn(e.getMessage() + "aborting redoRouting");
550 return false;
551 }
552 // route is from another edge to this edge-pair
553 if (targetRoutes.containsKey(targetSw)) {
554 targetRoutes.get(targetSw).add(route);
555 } else {
556 Set<ArrayList<DeviceId>> temp = new HashSet<>();
557 temp.add(route);
558 targetRoutes.put(targetSw, temp);
559 }
560 i.remove();
561 }
562 }
563 // so now for this edgepair we have a per target set of routechanges
564 // process target->edgePair route
565 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
566 targetRoutes.entrySet()) {
567 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
568 entry.getKey(), ep);
569 DeviceId targetSw = entry.getKey();
570 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
571 entry.getValue().forEach(route -> {
572 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
573 log.debug("route: target {} -> dst {} found with next-hops {}",
574 route.get(0), route.get(1), nhops);
575 perDstNextHops.put(route.get(1), nhops);
576 });
577 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
578 : subnets;
579 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
580 : subnets;
581 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
582 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
583 // handle routing to subnets common to edge-pair
584 // only if the targetSw is not part of the edge-pair
585 if (!ep.includes(targetSw)) {
586 if (!populateEcmpRoutingRulePartial(
587 targetSw,
588 ep.dev1, ep.dev2,
589 perDstNextHops,
590 Sets.intersection(ipDev1, ipDev2))) {
591 return false; // abort everything and fail fast
592 }
593 }
594 // handle routing to subnets that only belong to dev1
595 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
596 if (!onlyDev1Subnets.isEmpty() && perDstNextHops.get(ep.dev1) != null) {
597 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
598 onlyDev1NextHops.put(ep.dev1, perDstNextHops.get(ep.dev1));
599 if (!populateEcmpRoutingRulePartial(
600 targetSw,
601 ep.dev1, null,
602 onlyDev1NextHops,
603 onlyDev1Subnets)) {
604 return false; // abort everything and fail fast
605 }
606 }
607 // handle routing to subnets that only belong to dev2
608 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
609 if (!onlyDev2Subnets.isEmpty() && perDstNextHops.get(ep.dev2) != null) {
610 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
611 onlyDev2NextHops.put(ep.dev2, perDstNextHops.get(ep.dev2));
612 if (!populateEcmpRoutingRulePartial(
613 targetSw,
614 ep.dev2, null,
615 onlyDev2NextHops,
616 onlyDev2Subnets)) {
617 return false; // abort everything and fail fast
618 }
619 }
620 }
621 // if it gets here it has succeeded for all targets to this edge-pair
622 }
623 return true;
624 }
625
626 /**
627 * Programs targetSw in the changedRoutes for given prefixes reachable by
628 * a destination switch that is not part of an edge-pair.
629 * If no prefixes are given, the method will use configured subnets/prefixes.
630 *
631 * @param subnets a set of prefixes that need to be populated in the routing
632 * table of the target switch in the changedRoutes. Can be null,
633 * in which case all the configured prefixes belonging to the
634 * paired switches will be populated in the target switch
635 * @param changedRoutes a set of route-path changes, where each route-path is
636 * a list with its first element the src-switch (target)
637 * of the path, and the second element the dst-switch of
638 * the path.
639 * @return true if successful
640 */
641 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
642 Set<ArrayList<DeviceId>> changedRoutes) {
643 // aggregate route-path changes for each dst device
644 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
645 new HashMap<>();
646 for (ArrayList<DeviceId> route: changedRoutes) {
647 DeviceId dstSw = route.get(1);
648 ArrayList<ArrayList<DeviceId>> deviceRoutes =
649 routesBydevice.get(dstSw);
650 if (deviceRoutes == null) {
651 deviceRoutes = new ArrayList<>();
652 routesBydevice.put(dstSw, deviceRoutes);
653 }
654 deviceRoutes.add(route);
655 }
656 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
657 ArrayList<ArrayList<DeviceId>> deviceRoutes =
658 routesBydevice.get(impactedDstDevice);
659 for (ArrayList<DeviceId> route: deviceRoutes) {
660 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
661 route.get(0), route.get(1));
662 DeviceId targetSw = route.get(0);
663 DeviceId dstSw = route.get(1); // same as impactedDstDevice
664 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
665 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
666 nhops.put(dstSw, nextHops);
667 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
668 (subnets == null) ? Sets.newHashSet() : subnets)) {
669 return false; // abort routing and fail fast
670 }
671 log.debug("Populating flow rules from target: {} to dst: {}"
672 + " is successful", targetSw, dstSw);
673 }
674 //Only if all the flows for all impacted routes to a
675 //specific target are pushed successfully, update the
676 //ECMP graph for that target. Or else the next event
677 //would not see any changes in the ECMP graphs.
678 //In another case, the target switch has gone away, so
679 //routes can't be installed. In that case, the current map
680 //is updated here, without any flows being pushed.
681 currentEcmpSpgMap.put(impactedDstDevice,
682 updatedEcmpSpgMap.get(impactedDstDevice));
683 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
684 }
685 return true;
686 }
687
688 /**
689 * Populate ECMP rules for subnets from target to destination via nexthops.
690 *
691 * @param targetSw Device ID of target switch in which rules will be programmed
692 * @param destSw1 Device ID of final destination switch to which the rules will forward
693 * @param destSw2 Device ID of paired destination switch to which the rules will forward
694 * A null deviceId indicates packets should only be sent to destSw1
695 * @param nextHops Map indication a list of next hops per destSw
696 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
697 * @return true if it succeeds in populating rules
698 */ // refactor
699 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
700 DeviceId destSw1,
701 DeviceId destSw2,
702 Map<DeviceId, Set<DeviceId>> nextHops,
703 Set<IpPrefix> subnets) {
704 boolean result;
705 // If both target switch and dest switch are edge routers, then set IP
706 // rule for both subnet and router IP.
707 boolean targetIsEdge;
708 boolean dest1IsEdge;
709 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
710 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
711
712 try {
713 targetIsEdge = config.isEdgeDevice(targetSw);
714 dest1IsEdge = config.isEdgeDevice(destSw1);
715 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
716 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
717 if (destSw2 != null) {
718 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
719 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
720 }
721 } catch (DeviceConfigNotFoundException e) {
722 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Das62ae6792017-05-15 15:34:25 -0700723 return false;
724 }
Saurav Das261c3002017-06-13 15:35:54 -0700725
726 if (targetIsEdge && dest1IsEdge) {
727 subnets = (subnets != null && !subnets.isEmpty())
728 ? Sets.newHashSet(subnets)
729 : Sets.newHashSet(config.getSubnets(destSw1));
730 // XXX - Rethink this
731 /*subnets.add(dest1RouterIpv4.toIpPrefix());
732 if (dest1RouterIpv6 != null) {
733 subnets.add(dest1RouterIpv6.toIpPrefix());
734 }
735 if (destSw2 != null && dest2RouterIpv4 != null) {
736 subnets.add(dest2RouterIpv4.toIpPrefix());
737 if (dest2RouterIpv6 != null) {
738 subnets.add(dest2RouterIpv6.toIpPrefix());
739 }
740 }*/
741 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
742 + "for subnets {}", targetSw, destSw1,
743 (destSw2 != null) ? ("& " + destSw2) : "",
744 subnets);
745 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
746 destSw1, destSw2,
747 nextHops);
748 if (!result) {
749 return false;
750 }
751 /* XXX rethink this
752 IpPrefix routerIpPrefix = destRouterIpv4.toIpPrefix();
753 log.debug("* populateEcmpRoutingRulePartial in device {} towards {} "
754 + "for router IP {}", targetSw, destSw, routerIpPrefix);
755 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
756 destSw, nextHops);
757 if (!result) {
758 return false;
759 }
760 // If present we deal with IPv6 loopback.
761 if (destRouterIpv6 != null) {
762 routerIpPrefix = destRouterIpv6.toIpPrefix();
763 log.debug("* populateEcmpRoutingRulePartial in device {} towards {}"
764 + " for v6 router IP {}", targetSw, destSw, routerIpPrefix);
765 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
766 destSw, nextHops);
767 if (!result) {
768 return false;
769 }
770 }*/
Saurav Das62ae6792017-05-15 15:34:25 -0700771 }
Saurav Das261c3002017-06-13 15:35:54 -0700772
773 if (!targetIsEdge && dest1IsEdge) {
774 // MPLS rules in all non-edge target devices. These rules are for
775 // individual destinations, even if the dsts are part of edge-pairs.
776 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
777 + "all MPLS rules", targetSw, destSw1);
778 result = rulePopulator.populateMplsRule(targetSw, destSw1,
779 nextHops.get(destSw1),
780 dest1RouterIpv4);
781 if (!result) {
782 return false;
783 }
784 if (dest1RouterIpv6 != null) {
785 result = rulePopulator.populateMplsRule(targetSw, destSw1,
786 nextHops.get(destSw1),
787 dest1RouterIpv6);
788 if (!result) {
789 return false;
790 }
791 }
792 }
793
794 // To save on ECMP groups
795 // avoid MPLS rules in non-edge-devices to non-edge-devices
796 // avoid MPLS transit rules in edge-devices
797 // avoid loopback IP rules in edge-devices to non-edge-devices
798 return true;
Saurav Das62ae6792017-05-15 15:34:25 -0700799 }
800
801 /**
802 * Processes a set a route-path changes by editing hash groups.
803 *
804 * @param routeChanges a set of route-path changes, where each route-path is
805 * a list with its first element the src-switch of the path
806 * and the second element the dst-switch of the path.
807 * @param linkOrSwitchFailed true if the route changes are for a failed
808 * switch or linkDown event
809 * @param failedSwitch the switchId if the route changes are for a failed switch,
810 * otherwise null
811 */
812 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
813 boolean linkOrSwitchFailed,
814 DeviceId failedSwitch) {
815 for (ArrayList<DeviceId> route : routeChanges) {
816 DeviceId targetSw = route.get(0);
817 boolean success;
818 DeviceId dstSw = null;
819 if (route.size() > 1) {
820 dstSw = route.get(1);
821 }
822
823 if (linkOrSwitchFailed) {
Saurav Das261c3002017-06-13 15:35:54 -0700824 fixHashGroupsForRoute(route, true);
Saurav Das62ae6792017-05-15 15:34:25 -0700825 // it's possible that we cannot fix hash groups for a route
826 // if the target switch has failed. Nevertheless the ecmp graph
827 // for the impacted switch must still be updated.
828 if (failedSwitch != null && targetSw.equals(failedSwitch)
829 && dstSw != null) {
830 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
831 currentEcmpSpgMap.remove(targetSw);
832 log.debug("Updating ECMPspg for dst:{} removing failed "
833 + "target:{}", dstSw, targetSw);
834 return;
835 }
836 //linkfailed - update both sides
837 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das261c3002017-06-13 15:35:54 -0700838 if (dstSw != null) {
839 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
840 }
Saurav Das62ae6792017-05-15 15:34:25 -0700841 log.debug("Updating ECMPspg for dst:{} and target:{}", dstSw, targetSw);
842 } else {
843 success = fixHashGroupsForRoute(route, false);
844 if (success) {
845 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
846 if (dstSw != null) {
847 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
848 }
849 log.debug("Updating ECMPspg for target:{} and dst:{}",
850 targetSw, dstSw);
851 }
852 }
853 }
854 }
855
856 /**
857 * Edits hash groups in the src-switch (targetSw) of a route-path by
858 * calling the groupHandler to either add or remove buckets in an existing
859 * hash group.
860 *
861 * @param route a single list representing a route-path where the first element
862 * is the src-switch (targetSw) of the route-path and the
863 * second element is the dst-switch
864 * @param revoke true if buckets in the hash-groups need to be removed;
865 * false if buckets in the hash-groups need to be added
866 * @return true if the hash group editing is successful
867 */
868 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
869 boolean revoke) {
870 DeviceId targetSw = route.get(0);
871 if (route.size() < 2) {
872 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
873 return false;
874 }
875 DeviceId destSw = route.get(1);
876 log.debug("Processing fixHashGroupsForRoute: Target {} -> Dest {}",
877 targetSw, destSw);
878 boolean targetIsEdge = false;
879 try {
880 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
881 } catch (DeviceConfigNotFoundException e) {
882 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
883 + "continuing fixHash", targetSw);
884 }
885
886 // figure out the new next hops at the targetSw towards the destSw
887 Set<DeviceId> nextHops = new HashSet<>();
888 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(destSw);
889 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
890 ecmpSpg.getAllLearnedSwitchesAndVia();
891 for (Integer itrIdx : switchVia.keySet()) {
892 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
893 switchVia.get(itrIdx);
894 for (DeviceId target : swViaMap.keySet()) {
895 if (target.equals(targetSw)) {
896 // found the iteration where targetSw is reached- get nextHops
897 if (!targetIsEdge && itrIdx > 1) {
898 // optimization for spines to not use other leaves to get
899 // to a leaf to avoid loops
900 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
901 + " --> dstSw:{}", itrIdx, targetSw, destSw);
902 break;
903 }
904 for (ArrayList<DeviceId> via : swViaMap.get(target)) {
905 if (via.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700906 // the dstSw is the next-hop from the targetSw
Saurav Das62ae6792017-05-15 15:34:25 -0700907 nextHops.add(destSw);
908 } else {
909 // first elem is next-hop in each ECMP path
910 nextHops.add(via.get(0));
911 }
912 }
913 break;
914 }
915 }
916 }
917
918 // call group handler to change hash group at targetSw
919 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
920 if (grpHandler == null) {
921 log.warn("Cannot find grouphandler for dev:{} .. aborting"
922 + " {} hash group buckets for route:{} ", targetSw,
923 (revoke) ? "revoke" : "repopulate", route);
924 return false;
925 }
926 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
927 (revoke) ? "revoke" : "repopulating",
928 targetSw, destSw, nextHops);
929 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
930 destSw, true)
931 : grpHandler.fixHashGroups(targetSw, nextHops,
932 destSw, false);
933 }
934
935 /**
Saurav Das261c3002017-06-13 15:35:54 -0700936 * Start the flow rule population process if it was never started. The
937 * process finishes successfully when all flow rules are set and stops with
938 * ABORTED status when any groups required for flows is not set yet.
Saurav Das62ae6792017-05-15 15:34:25 -0700939 */
Saurav Das261c3002017-06-13 15:35:54 -0700940 public void startPopulationProcess() {
941 statusLock.lock();
942 try {
943 if (populationStatus == Status.IDLE
944 || populationStatus == Status.SUCCEEDED
945 || populationStatus == Status.ABORTED) {
946 populateAllRoutingRules();
sangho28d0b6d2015-05-07 13:30:57 -0700947 } else {
Saurav Das261c3002017-06-13 15:35:54 -0700948 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
949 populationStatus);
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -0700950 }
Saurav Das261c3002017-06-13 15:35:54 -0700951 } finally {
952 statusLock.unlock();
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -0700953 }
sanghofb7c7292015-04-13 15:15:58 -0700954 }
955
Saurav Dasb149be12016-06-07 10:08:06 -0700956 /**
Saurav Das261c3002017-06-13 15:35:54 -0700957 * Revoke rules of given subnet in all edge switches.
958 *
959 * @param subnets subnet being removed
960 * @return true if succeed
961 */
962 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
963 statusLock.lock();
964 try {
965 return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
966 } finally {
967 statusLock.unlock();
968 }
969 }
970
971 /**
972 * Remove ECMP graph entry for the given device. Typically called when
973 * device is no longer available.
974 *
975 * @param deviceId the device for which graphs need to be purged
976 */
977 protected void purgeEcmpGraph(DeviceId deviceId) {
978 currentEcmpSpgMap.remove(deviceId);
979 if (updatedEcmpSpgMap != null) {
980 updatedEcmpSpgMap.remove(deviceId);
981 }
982 }
983
984 //////////////////////////////////////
985 // Routing helper methods and classes
986 //////////////////////////////////////
987
988 /**
Saurav Das1b391d52016-11-29 14:27:25 -0800989 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb149be12016-06-07 10:08:06 -0700990 * previous ecmp shortest-path graph exists for a switch in order to compute
991 * affected routes. If such a graph does not exist, the method returns null.
992 *
993 * @param linkFail the failed link
994 * @return the set of affected routes which may be empty if no routes were
995 * affected, or null if no previous ecmp spg was found for comparison
996 */
sanghofb7c7292015-04-13 15:15:58 -0700997 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
998
999 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1000
1001 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001002 log.debug("Computing the impacted routes for device {} due to link fail",
1003 sw.id());
Charles Chan77277672015-10-20 16:24:19 -07001004 if (!srManager.mastershipService.isLocalMaster(sw.id())) {
Saurav Dasb149be12016-06-07 10:08:06 -07001005 log.debug("No mastership for {} .. skipping route optimization",
1006 sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001007 continue;
1008 }
Shashikanth VH0637b162015-12-11 01:32:44 +05301009 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001010 if (ecmpSpg == null) {
Saurav Dasb149be12016-06-07 10:08:06 -07001011 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1012 + " rerouting and opting for full-reroute", sw.id());
1013 return null;
sanghofb7c7292015-04-13 15:15:58 -07001014 }
Saurav Das62ae6792017-05-15 15:34:25 -07001015 if (log.isDebugEnabled()) {
1016 log.debug("Root switch: {}", sw.id());
1017 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1018 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(sw.id()));
1019 }
sanghofb7c7292015-04-13 15:15:58 -07001020 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1021 ecmpSpg.getAllLearnedSwitchesAndVia();
1022 for (Integer itrIdx : switchVia.keySet()) {
Saurav Das62ae6792017-05-15 15:34:25 -07001023 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
sanghofb7c7292015-04-13 15:15:58 -07001024 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1025 switchVia.get(itrIdx);
1026 for (DeviceId targetSw : swViaMap.keySet()) {
Saurav Das1b391d52016-11-29 14:27:25 -08001027 DeviceId rootSw = sw.id();
Saurav Dasb149be12016-06-07 10:08:06 -07001028 if (log.isTraceEnabled()) {
Saurav Das1b391d52016-11-29 14:27:25 -08001029 log.trace("TargetSwitch {} --> RootSwitch {}", targetSw, rootSw);
Saurav Dasb149be12016-06-07 10:08:06 -07001030 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1031 log.trace(" Via:");
Pier Ventreadb4ae62016-11-23 09:57:42 -08001032 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb149be12016-06-07 10:08:06 -07001033 }
1034 }
sanghofb7c7292015-04-13 15:15:58 -07001035 Set<ArrayList<DeviceId>> subLinks =
Saurav Das1b391d52016-11-29 14:27:25 -08001036 computeLinks(targetSw, rootSw, swViaMap);
sanghofb7c7292015-04-13 15:15:58 -07001037 for (ArrayList<DeviceId> alink: subLinks) {
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001038 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1039 alink.get(1).equals(linkFail.dst().deviceId()))
1040 ||
1041 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1042 alink.get(1).equals(linkFail.src().deviceId()))) {
Saurav Das1b391d52016-11-29 14:27:25 -08001043 log.debug("Impacted route:{}->{}", targetSw, rootSw);
sanghofb7c7292015-04-13 15:15:58 -07001044 ArrayList<DeviceId> aRoute = new ArrayList<>();
Saurav Das62ae6792017-05-15 15:34:25 -07001045 aRoute.add(targetSw); // switch with rules to populate
1046 aRoute.add(rootSw); // towards this destination
sanghofb7c7292015-04-13 15:15:58 -07001047 routes.add(aRoute);
1048 break;
1049 }
1050 }
1051 }
1052 }
sangho28d0b6d2015-05-07 13:30:57 -07001053
sanghofb7c7292015-04-13 15:15:58 -07001054 }
1055
1056 return routes;
1057 }
1058
Saurav Das1b391d52016-11-29 14:27:25 -08001059 /**
1060 * Computes set of affected routes due to new links or failed switches.
1061 *
1062 * @return the set of affected routes which may be empty if no routes were
1063 * affected
1064 */
sanghofb7c7292015-04-13 15:15:58 -07001065 private Set<ArrayList<DeviceId>> computeRouteChange() {
Saurav Das261c3002017-06-13 15:35:54 -07001066 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das1b391d52016-11-29 14:27:25 -08001067 ImmutableSet.builder();
sanghofb7c7292015-04-13 15:15:58 -07001068
1069 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das261c3002017-06-13 15:35:54 -07001070 log.debug("Computing the impacted routes for device {}", sw.id());
1071 DeviceId retId = shouldHandleRouting(sw.id());
1072 if (retId == null) {
sanghofb7c7292015-04-13 15:15:58 -07001073 continue;
1074 }
Saurav Das261c3002017-06-13 15:35:54 -07001075 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1076 for (DeviceId rootSw : devicesToProcess) {
1077 if (log.isTraceEnabled()) {
1078 log.trace("Device links for dev: {}", rootSw);
1079 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1080 log.trace("{} -> {} ", link.src().deviceId(),
1081 link.dst().deviceId());
1082 }
Saurav Dasb149be12016-06-07 10:08:06 -07001083 }
Saurav Das261c3002017-06-13 15:35:54 -07001084 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1085 if (currEcmpSpg == null) {
1086 log.debug("No existing ECMP graph for device {}.. adding self as "
1087 + "changed route", rootSw);
1088 changedRtBldr.add(Lists.newArrayList(rootSw));
1089 continue;
1090 }
1091 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
1092 if (log.isDebugEnabled()) {
1093 log.debug("Root switch: {}", rootSw);
1094 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1095 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1096 }
1097 // first use the updated/new map to compare to current/existing map
1098 // as new links may have come up
1099 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1100 // then use the current/existing map to compare to updated/new map
1101 // as switch may have been removed
1102 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho28d0b6d2015-05-07 13:30:57 -07001103 }
Saurav Das1b391d52016-11-29 14:27:25 -08001104 }
sanghofb7c7292015-04-13 15:15:58 -07001105
Saurav Das261c3002017-06-13 15:35:54 -07001106 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das1b391d52016-11-29 14:27:25 -08001107 for (ArrayList<DeviceId> route: changedRoutes) {
1108 log.debug("Route changes Target -> Root");
1109 if (route.size() == 1) {
1110 log.debug(" : all -> {}", route.get(0));
1111 } else {
1112 log.debug(" : {} -> {}", route.get(0), route.get(1));
1113 }
1114 }
1115 return changedRoutes;
1116 }
1117
1118 /**
1119 * For the root switch, searches all the target nodes reachable in the base
1120 * graph, and compares paths to the ones in the comp graph.
1121 *
1122 * @param base the graph that is indexed for all reachable target nodes
1123 * from the root node
1124 * @param comp the graph that the base graph is compared to
1125 * @param rootSw both ecmp graphs are calculated for the root node
1126 * @return all the routes that have changed in the base graph
1127 */
1128 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1129 EcmpShortestPathGraph comp,
1130 DeviceId rootSw) {
1131 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1132 ImmutableSet.builder();
1133 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1134 base.getAllLearnedSwitchesAndVia();
1135 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1136 comp.getAllLearnedSwitchesAndVia();
1137 for (Integer itrIdx : baseMap.keySet()) {
1138 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1139 baseMap.get(itrIdx);
1140 for (DeviceId targetSw : baseViaMap.keySet()) {
1141 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1142 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1143 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001144 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das1b391d52016-11-29 14:27:25 -08001145 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das261c3002017-06-13 15:35:54 -07001146 route.add(targetSw); // switch with rules to populate
1147 route.add(rootSw); // towards this destination
Saurav Das1b391d52016-11-29 14:27:25 -08001148 changedRoutesBuilder.add(route);
sanghofb7c7292015-04-13 15:15:58 -07001149 }
1150 }
sangho28d0b6d2015-05-07 13:30:57 -07001151 }
Saurav Das1b391d52016-11-29 14:27:25 -08001152 return changedRoutesBuilder.build();
sanghofb7c7292015-04-13 15:15:58 -07001153 }
1154
Saurav Das261c3002017-06-13 15:35:54 -07001155 /**
1156 * Returns the ECMP paths traversed to reach the target switch.
1157 *
1158 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1159 * @param targetSw the switch to reach from the root switch
1160 * @return the nodes traversed on ECMP paths to the target switch
1161 */
sanghofb7c7292015-04-13 15:15:58 -07001162 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das1b391d52016-11-29 14:27:25 -08001163 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sanghofb7c7292015-04-13 15:15:58 -07001164 for (Integer itrIdx : switchVia.keySet()) {
1165 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1166 switchVia.get(itrIdx);
Saurav Das1b391d52016-11-29 14:27:25 -08001167 if (swViaMap.get(targetSw) == null) {
sanghofb7c7292015-04-13 15:15:58 -07001168 continue;
1169 } else {
Saurav Das1b391d52016-11-29 14:27:25 -08001170 return swViaMap.get(targetSw);
sanghofb7c7292015-04-13 15:15:58 -07001171 }
1172 }
1173
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001174 return null;
sanghofb7c7292015-04-13 15:15:58 -07001175 }
1176
Saurav Das261c3002017-06-13 15:35:54 -07001177 /**
1178 * Utility method to break down a path from src to dst device into a collection
1179 * of links.
1180 *
1181 * @param src src device of the path
1182 * @param dst dst device of the path
1183 * @param viaMap path taken from src to dst device
1184 * @return collection of links in the path
1185 */
sanghofb7c7292015-04-13 15:15:58 -07001186 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1187 DeviceId dst,
1188 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1189 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1190 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1191 DeviceId linkSrc = src;
1192 DeviceId linkDst = dst;
1193 for (DeviceId viaDevice: via) {
1194 ArrayList<DeviceId> link = new ArrayList<>();
1195 linkDst = viaDevice;
1196 link.add(linkSrc);
1197 link.add(linkDst);
1198 subLinks.add(link);
1199 linkSrc = viaDevice;
1200 }
1201 ArrayList<DeviceId> link = new ArrayList<>();
1202 link.add(linkSrc);
1203 link.add(dst);
1204 subLinks.add(link);
1205 }
1206
1207 return subLinks;
1208 }
1209
Charles Chanc22cef32016-04-29 14:38:22 -07001210 /**
Saurav Das261c3002017-06-13 15:35:54 -07001211 * Determines whether this controller instance should handle routing for the
1212 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
1213 * Returns null if this instance should not handle routing for given {@code deviceId}.
1214 * Otherwise the returned value could be the given deviceId itself, or the
1215 * deviceId for the paired edge device. In the latter case, this instance
1216 * should handle routing for both the given device and the paired device.
Charles Chanc22cef32016-04-29 14:38:22 -07001217 *
Saurav Das261c3002017-06-13 15:35:54 -07001218 * @param deviceId device identifier to consider for routing
1219 * @return null or deviceId which could be the same as the given deviceId
1220 * or the deviceId of a paired edge device
Charles Chanc22cef32016-04-29 14:38:22 -07001221 */
Saurav Das261c3002017-06-13 15:35:54 -07001222 private DeviceId shouldHandleRouting(DeviceId deviceId) {
1223 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1224 log.debug("Not master for dev:{} .. skipping routing, may get handled "
1225 + "elsewhere as part of paired devices", deviceId);
1226 return null;
1227 }
1228 NodeId myNode = srManager.mastershipService.getMasterFor(deviceId);
1229 DeviceId pairDev = getPairDev(deviceId);
sangho80f11cb2015-04-01 13:05:26 -07001230
Saurav Das261c3002017-06-13 15:35:54 -07001231 if (pairDev != null) {
1232 if (!srManager.deviceService.isAvailable(pairDev)) {
1233 log.warn("pairedDev {} not available .. routing this dev:{} "
1234 + "without mastership check",
1235 pairDev, deviceId);
1236 return pairDev; // handle both temporarily
1237 }
1238 NodeId pairMasterNode = srManager.mastershipService.getMasterFor(pairDev);
1239 if (myNode.compareTo(pairMasterNode) <= 0) {
1240 log.debug("Handling routing for both dev:{} pair-dev:{}; myNode: {}"
1241 + " pairMaster:{} compare:{}", deviceId, pairDev,
1242 myNode, pairMasterNode,
1243 myNode.compareTo(pairMasterNode));
1244 return pairDev; // handle both
1245 } else {
1246 log.debug("PairDev node: {} should handle routing for dev:{} and "
1247 + "pair-dev:{}", pairMasterNode, deviceId, pairDev);
1248 return null; // handle neither
sangho80f11cb2015-04-01 13:05:26 -07001249 }
1250 }
Saurav Das261c3002017-06-13 15:35:54 -07001251 return deviceId; // not paired, just handle given device
sangho80f11cb2015-04-01 13:05:26 -07001252 }
1253
Charles Chanc22cef32016-04-29 14:38:22 -07001254 /**
Saurav Das261c3002017-06-13 15:35:54 -07001255 * Returns the configured paired DeviceId for the given Device, or null
1256 * if no such paired device has been configured.
Charles Chanc22cef32016-04-29 14:38:22 -07001257 *
Saurav Das261c3002017-06-13 15:35:54 -07001258 * @param deviceId
1259 * @return configured pair deviceId or null
Charles Chanc22cef32016-04-29 14:38:22 -07001260 */
Saurav Das261c3002017-06-13 15:35:54 -07001261 private DeviceId getPairDev(DeviceId deviceId) {
1262 DeviceId pairDev;
Charles Chan319d1a22015-11-03 10:42:14 -08001263 try {
Saurav Das261c3002017-06-13 15:35:54 -07001264 pairDev = srManager.deviceConfiguration.getPairDeviceId(deviceId);
Charles Chan319d1a22015-11-03 10:42:14 -08001265 } catch (DeviceConfigNotFoundException e) {
Saurav Das261c3002017-06-13 15:35:54 -07001266 log.warn(e.getMessage() + " .. cannot continue routing for dev: {}");
1267 return null;
Charles Chan319d1a22015-11-03 10:42:14 -08001268 }
Saurav Das261c3002017-06-13 15:35:54 -07001269 return pairDev;
sangho80f11cb2015-04-01 13:05:26 -07001270 }
1271
1272 /**
Saurav Das261c3002017-06-13 15:35:54 -07001273 * Returns the set of deviceIds which are the next hops from the targetSw
1274 * to the dstSw according to the latest ECMP spg.
1275 *
1276 * @param targetSw the switch for which the next-hops are desired
1277 * @param dstSw the switch to which the next-hops lead to from the targetSw
1278 * @return set of next hop deviceIds, could be empty if no next hops are found
1279 */
1280 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1281 boolean targetIsEdge = false;
1282 try {
1283 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1284 } catch (DeviceConfigNotFoundException e) {
1285 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1286 + "continuing to getNextHops", targetSw);
1287 }
1288
1289 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1290 if (ecmpSpg == null) {
1291 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1292 return ImmutableSet.of();
1293 }
1294 HashMap<Integer,
1295 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1296 ecmpSpg.getAllLearnedSwitchesAndVia();
1297 for (Integer itrIdx : switchVia.keySet()) {
1298 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1299 switchVia.get(itrIdx);
1300 for (DeviceId target : swViaMap.keySet()) {
1301 if (!target.equals(targetSw)) {
1302 continue;
1303 }
1304 if (!targetIsEdge && itrIdx > 1) {
1305 // optimization for spines to not use other leaves to get
1306 // to a leaf to avoid loops
1307 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1308 + " --> dstSw:{}", itrIdx, targetSw, dstSw);
1309 break;
1310 }
1311 Set<DeviceId> nextHops = new HashSet<>();
1312 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1313 if (via.isEmpty()) {
1314 // the dstSw is the next-hop from the targetSw
1315 nextHops.add(dstSw);
1316 } else {
1317 // first elem is next-hop in each ECMP path
1318 nextHops.add(via.get(0));
1319 }
1320 }
1321 return nextHops;
1322 }
1323 }
1324 return ImmutableSet.of(); //no next-hops found
1325 }
1326
1327 /**
1328 * Represents two devices that are paired by configuration. An EdgePair for
1329 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1330 */
1331 protected final class EdgePair {
1332 DeviceId dev1;
1333 DeviceId dev2;
1334
1335 EdgePair(DeviceId dev1, DeviceId dev2) {
1336 this.dev1 = dev1;
1337 this.dev2 = dev2;
1338 }
1339
1340 boolean includes(DeviceId dev) {
1341 return dev1.equals(dev) || dev2.equals(dev);
1342 }
1343
1344 @Override
1345 public boolean equals(Object o) {
1346 if (this == o) {
1347 return true;
1348 }
1349 if (!(o instanceof EdgePair)) {
1350 return false;
1351 }
1352 EdgePair that = (EdgePair) o;
1353 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1354 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1355 }
1356
1357 @Override
1358 public int hashCode() {
1359 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1360 return Objects.hash(dev1, dev2);
1361 } else {
1362 return Objects.hash(dev2, dev1);
1363 }
1364 }
1365
1366 @Override
1367 public String toString() {
1368 return toStringHelper(this)
1369 .add("Dev1", dev1)
1370 .add("Dev2", dev2)
1371 .toString();
1372 }
1373 }
1374
1375 //////////////////////////////////////
1376 // Filtering rule creation
1377 //////////////////////////////////////
1378
1379 /**
Saurav Dasf9332192017-02-18 14:05:44 -08001380 * Populates filtering rules for port, and punting rules
1381 * for gateway IPs, loopback IPs and arp/ndp traffic.
1382 * Should only be called by the master instance for this device/port.
sangho80f11cb2015-04-01 13:05:26 -07001383 *
1384 * @param deviceId Switch ID to set the rules
1385 */
Saurav Das9f1c42e2015-10-23 10:51:11 -07001386 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das07c74602016-04-27 18:35:50 -07001387 // Although device is added, sometimes device store does not have the
1388 // ports for this device yet. It results in missing filtering rules in the
1389 // switch. We will attempt it a few times. If it still does not work,
1390 // user can manually repopulate using CLI command sr-reroute-network
Charles Chan18fa4252017-02-08 16:10:40 -08001391 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001392 if (firstRun == null) {
1393 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das07c74602016-04-27 18:35:50 -07001394 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001395 executorService.schedule(new RetryFilters(deviceId, firstRun),
1396 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sangho80f11cb2015-04-01 13:05:26 -07001397 }
1398
1399 /**
Saurav Dasd1872b02016-12-02 15:43:47 -08001400 * Utility class used to temporarily store information about the ports on a
1401 * device processed for filtering objectives.
Saurav Dasd1872b02016-12-02 15:43:47 -08001402 */
1403 public final class PortFilterInfo {
Saurav Dasf9332192017-02-18 14:05:44 -08001404 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das07c74602016-04-27 18:35:50 -07001405
Saurav Dasf9332192017-02-18 14:05:44 -08001406 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd1872b02016-12-02 15:43:47 -08001407 int filteredPorts) {
1408 this.disabledPorts = disabledPorts;
1409 this.filteredPorts = filteredPorts;
Saurav Dasf9332192017-02-18 14:05:44 -08001410 this.errorPorts = errorPorts;
Saurav Dasd1872b02016-12-02 15:43:47 -08001411 }
1412
1413 @Override
1414 public int hashCode() {
Saurav Dasf9332192017-02-18 14:05:44 -08001415 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd1872b02016-12-02 15:43:47 -08001416 }
1417
1418 @Override
1419 public boolean equals(Object obj) {
1420 if (this == obj) {
1421 return true;
1422 }
1423 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1424 return false;
1425 }
1426 PortFilterInfo other = (PortFilterInfo) obj;
1427 return ((disabledPorts == other.disabledPorts) &&
1428 (filteredPorts == other.filteredPorts) &&
Saurav Dasf9332192017-02-18 14:05:44 -08001429 (errorPorts == other.errorPorts));
Saurav Dasd1872b02016-12-02 15:43:47 -08001430 }
1431
1432 @Override
1433 public String toString() {
1434 MoreObjects.ToStringHelper helper = toStringHelper(this)
1435 .add("disabledPorts", disabledPorts)
Saurav Dasf9332192017-02-18 14:05:44 -08001436 .add("errorPorts", errorPorts)
Saurav Dasd1872b02016-12-02 15:43:47 -08001437 .add("filteredPorts", filteredPorts);
1438 return helper.toString();
1439 }
1440 }
1441
1442 /**
1443 * RetryFilters populates filtering objectives for a device and keeps retrying
1444 * till the number of ports filtered are constant for a predefined number
1445 * of attempts.
1446 */
1447 protected final class RetryFilters implements Runnable {
1448 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1449 DeviceId devId;
1450 int counter;
1451 PortFilterInfo prevRun;
1452
1453 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das07c74602016-04-27 18:35:50 -07001454 devId = deviceId;
Saurav Dasd1872b02016-12-02 15:43:47 -08001455 prevRun = previousRun;
1456 counter = 0;
Saurav Das07c74602016-04-27 18:35:50 -07001457 }
1458
1459 @Override
1460 public void run() {
Charles Chan077314e2017-06-22 14:27:17 -07001461 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chan18fa4252017-02-08 16:10:40 -08001462 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001463 boolean sameResult = prevRun.equals(thisRun);
1464 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1465 thisRun, sameResult);
1466 if (thisRun == null || !sameResult || (sameResult && --constantAttempts > 0)) {
Saurav Dasf9332192017-02-18 14:05:44 -08001467 // exponentially increasing intervals for retries
1468 executorService.schedule(this,
1469 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1470 TimeUnit.MILLISECONDS);
Saurav Dasd1872b02016-12-02 15:43:47 -08001471 if (!sameResult) {
1472 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1473 }
Saurav Das07c74602016-04-27 18:35:50 -07001474 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001475 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das07c74602016-04-27 18:35:50 -07001476 }
Saurav Das07c74602016-04-27 18:35:50 -07001477 }
1478
sangho80f11cb2015-04-01 13:05:26 -07001479}