blob: 52b02a3024f24ce6b4f3a4e9bb8f14e273a1bef9 [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
sangho666cd6d2015-04-14 16:27:13 -070026import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080027import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070028import org.onlab.packet.IpPrefix;
Charles Chan2fde6d42017-08-23 14:46:43 -070029import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
Saurav Das7bcbe702017-06-13 15:35:54 -070031import org.onosproject.cluster.NodeId;
Charles Chan93e71ba2016-04-29 14:38:22 -070032import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070033import org.onosproject.net.Device;
34import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070035import org.onosproject.net.Link;
Charles Chan2fde6d42017-08-23 14:46:43 -070036import org.onosproject.net.PortNumber;
Charles Chan0b4e6182015-11-03 10:42:14 -080037import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
38import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070039import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Charles Chan2ff1bac2018-03-29 16:03:41 -070040import org.onosproject.store.serializers.KryoNamespaces;
41import org.onosproject.store.service.Serializer;
sanghob35a6192015-04-01 13:05:26 -070042import org.slf4j.Logger;
43import org.slf4j.LoggerFactory;
44
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070045import java.time.Instant;
sanghob35a6192015-04-01 13:05:26 -070046import java.util.ArrayList;
Charles Chan2ff1bac2018-03-29 16:03:41 -070047import java.util.Collections;
sanghob35a6192015-04-01 13:05:26 -070048import java.util.HashMap;
49import java.util.HashSet;
Saurav Das7bcbe702017-06-13 15:35:54 -070050import java.util.Iterator;
Charles Chan2ff1bac2018-03-29 16:03:41 -070051import java.util.List;
Saurav Das7bcbe702017-06-13 15:35:54 -070052import java.util.Map;
Saurav Dasd2fded02016-12-02 15:43:47 -080053import java.util.Objects;
Charles Chanba6c5752018-04-02 11:46:38 -070054import java.util.Optional;
sanghob35a6192015-04-01 13:05:26 -070055import java.util.Set;
Saurav Das59232cf2016-04-27 18:35:50 -070056import java.util.concurrent.ScheduledExecutorService;
57import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090058import java.util.concurrent.locks.Lock;
59import java.util.concurrent.locks.ReentrantLock;
Saurav Das604ab3a2018-03-18 21:28:15 -070060import java.util.stream.Stream;
61
Saurav Dasd2fded02016-12-02 15:43:47 -080062import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventree0ae7a32016-11-23 09:57:42 -080063import static com.google.common.base.Preconditions.checkNotNull;
64import static java.util.concurrent.Executors.newScheduledThreadPool;
65import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070066
Charles Chane849c192016-01-11 18:28:54 -080067/**
68 * Default routing handler that is responsible for route computing and
69 * routing rule population.
70 */
sanghob35a6192015-04-01 13:05:26 -070071public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080072 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey3717e602018-02-01 13:49:47 -080073 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Das018605f2017-02-18 14:05:44 -080074 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070075 private static final long STABLITY_THRESHOLD = 10; //secs
Charles Chan93e71ba2016-04-29 14:38:22 -070076 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070077
78 private SegmentRoutingManager srManager;
79 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053080 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
81 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070082 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090083 private final Lock statusLock = new ReentrantLock();
84 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070085 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080086 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070087 private Instant lastRoutingChange;
sanghob35a6192015-04-01 13:05:26 -070088
Charles Chan2ff1bac2018-03-29 16:03:41 -070089 // Keep track on which ONOS instance should program the device pair.
90 // There should be only one instance that programs the same pair.
91 Map<Set<DeviceId>, NodeId> shouldProgram;
92
sanghob35a6192015-04-01 13:05:26 -070093 /**
94 * Represents the default routing population status.
95 */
96 public enum Status {
97 // population process is not started yet.
98 IDLE,
99
100 // population process started.
101 STARTED,
102
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700103 // population process was aborted due to errors, mostly for groups not
104 // found.
sanghob35a6192015-04-01 13:05:26 -0700105 ABORTED,
106
107 // population process was finished successfully.
108 SUCCEEDED
109 }
110
111 /**
112 * Creates a DefaultRoutingHandler object.
113 *
114 * @param srManager SegmentRoutingManager object
115 */
Charles Chan2ff1bac2018-03-29 16:03:41 -0700116 DefaultRoutingHandler(SegmentRoutingManager srManager) {
sanghob35a6192015-04-01 13:05:26 -0700117 this.srManager = srManager;
118 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700119 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700120 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700121 this.currentEcmpSpgMap = Maps.newHashMap();
Charles Chan2ff1bac2018-03-29 16:03:41 -0700122 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
123 .withName("sr-should-program")
124 .withSerializer(Serializer.using(KryoNamespaces.API))
125 .build().asJavaMap();
sanghob35a6192015-04-01 13:05:26 -0700126 }
127
128 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700129 * Returns an immutable copy of the current ECMP shortest-path graph as
130 * computed by this controller instance.
131 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700132 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700133 */
134 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
135 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
136 currentEcmpSpgMap.entrySet().forEach(entry -> {
137 if (entry.getValue() != null) {
138 builder.put(entry.getKey(), entry.getValue());
139 }
140 });
141 return builder.build();
142 }
143
Saurav Dasceccf242017-08-03 18:30:35 -0700144 /**
145 * Acquires the lock used when making routing changes.
146 */
147 public void acquireRoutingLock() {
148 statusLock.lock();
149 }
150
151 /**
152 * Releases the lock used when making routing changes.
153 */
154 public void releaseRoutingLock() {
155 statusLock.unlock();
156 }
157
158 /**
159 * Determines if routing in the network has been stable in the last
160 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
161 * routing change timestamp.
162 *
163 * @return true if stable
164 */
165 public boolean isRoutingStable() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700166 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
167 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700168 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700169 return (now - last) > STABLITY_THRESHOLD;
170 }
171
172
Saurav Das7bcbe702017-06-13 15:35:54 -0700173 //////////////////////////////////////
174 // Route path handling
175 //////////////////////////////////////
176
Saurav Das45f48152018-01-18 12:07:33 -0800177 /* The following three methods represent the three major ways in which
178 * route-path handling is triggered in the network
Saurav Das7bcbe702017-06-13 15:35:54 -0700179 * a) due to configuration change
180 * b) due to route-added event
181 * c) due to change in the topology
182 */
183
Saurav Dasc88d4662017-05-15 15:34:25 -0700184 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700185 * Populates all routing rules to all switches. Typically triggered at
186 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700187 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700188 public void populateAllRoutingRules() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700189 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900190 statusLock.lock();
191 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700192 if (populationStatus == Status.STARTED) {
193 log.warn("Previous rule population is not finished. Cannot"
194 + " proceed with populateAllRoutingRules");
195 return;
196 }
197
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900198 populationStatus = Status.STARTED;
199 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700200 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900201 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700202
Saurav Das7bcbe702017-06-13 15:35:54 -0700203 // take a snapshot of the topology
204 updatedEcmpSpgMap = new HashMap<>();
205 Set<EdgePair> edgePairs = new HashSet<>();
206 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800207 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700208 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800209 new EcmpShortestPathGraph(dstSw, srManager);
210 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700211 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
212 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700213 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700214 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
215 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
216 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700217 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700218
219 if (!shouldProgram(dstSw)) {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900220 continue;
221 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700222 // To do a full reroute, assume all routes have changed
Charles Chan2ff1bac2018-03-29 16:03:41 -0700223 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800224 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
225 if (targetSw.equals(dev)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700226 continue;
227 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800228 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700229 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900230 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700231 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900232
Saurav Das7bcbe702017-06-13 15:35:54 -0700233 if (!redoRouting(routeChanges, edgePairs, null)) {
234 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
235 populationStatus = Status.ABORTED;
236 log.warn("Failed to repopulate all routing rules.");
237 return;
sanghob35a6192015-04-01 13:05:26 -0700238 }
239
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900240 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
241 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700242 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900243 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700244 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900245 } finally {
246 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700247 }
sanghob35a6192015-04-01 13:05:26 -0700248 }
249
sangho20eff1d2015-04-13 15:15:58 -0700250 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700251 * Populate rules from all other edge devices to the connect-point(s)
252 * specified for the given subnets.
253 *
254 * @param cpts connect point(s) of the subnets being added
255 * @param subnets subnets being added
Charles Chan2fde6d42017-08-23 14:46:43 -0700256 */
257 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700258 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan71e64f12017-09-11 15:21:57 -0700259 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
260 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
261 return;
262 }
263
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700264 lastRoutingChange = Instant.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700265 statusLock.lock();
266 try {
267 if (populationStatus == Status.STARTED) {
268 log.warn("Previous rule population is not finished. Cannot"
269 + " proceed with routing rules for added routes");
270 return;
271 }
272 populationStatus = Status.STARTED;
273 rulePopulator.resetCounter();
Charles Chan2fde6d42017-08-23 14:46:43 -0700274 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
275 subnets, cpts);
Saurav Dasc568c342018-01-25 09:49:01 -0800276 // In principle an update to a subnet/prefix should not require a
277 // new ECMPspg calculation as it is not a topology event. As a
278 // result, we use the current/existing ECMPspg in the updated map
279 // used by the redoRouting method.
Saurav Das15a81782018-02-09 09:15:03 -0800280 if (updatedEcmpSpgMap == null) {
281 updatedEcmpSpgMap = new HashMap<>();
282 }
Saurav Dasc568c342018-01-25 09:49:01 -0800283 currentEcmpSpgMap.entrySet().forEach(entry -> {
284 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase7f51012018-02-09 17:26:45 -0800285 if (log.isTraceEnabled()) {
286 log.trace("Root switch: {}", entry.getKey());
287 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Dasc568c342018-01-25 09:49:01 -0800288 }
289 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700290 Set<EdgePair> edgePairs = new HashSet<>();
291 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
292 boolean handleRouting = false;
293
294 if (cpts.size() == 2) {
295 // ensure connect points are edge-pairs
296 Iterator<ConnectPoint> iter = cpts.iterator();
297 DeviceId dev1 = iter.next().deviceId();
Charles Chanba6c5752018-04-02 11:46:38 -0700298 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
299 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
300 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700301 } else {
302 log.warn("Connectpoints {} for subnets {} not on "
303 + "pair-devices.. aborting populateSubnet", cpts, subnets);
304 populationStatus = Status.ABORTED;
305 return;
306 }
307 for (ConnectPoint cp : cpts) {
Saurav Dasc568c342018-01-25 09:49:01 -0800308 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
309 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700310 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800311 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
312 log.warn("populateSubnet: no updated graph for dev:{}"
313 + " ... creating", cp.deviceId());
314 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700315 if (!shouldProgram(cp.deviceId())) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700316 continue;
317 }
318 handleRouting = true;
319 }
320 } else {
321 // single connect point
322 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Dasc568c342018-01-25 09:49:01 -0800323 if (updatedEcmpSpgMap.get(dstSw) == null) {
324 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700325 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800326 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
327 log.warn("populateSubnet: no updated graph for dev:{}"
328 + " ... creating", dstSw);
329 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700330 handleRouting = shouldProgram(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700331 }
332
333 if (!handleRouting) {
334 log.debug("This instance is not handling ecmp routing to the "
335 + "connectPoint(s) {}", cpts);
336 populationStatus = Status.ABORTED;
337 return;
338 }
339
340 // if it gets here, this instance should handle routing for the
341 // connectpoint(s). Assume all route-paths have to be updated to
342 // the connectpoint(s) with the following exceptions
343 // 1. if target is non-edge no need for routing rules
344 // 2. if target is one of the connectpoints
345 for (ConnectPoint cp : cpts) {
346 DeviceId dstSw = cp.deviceId();
347 for (Device targetSw : srManager.deviceService.getDevices()) {
348 boolean isEdge = false;
349 try {
350 isEdge = config.isEdgeDevice(targetSw.id());
351 } catch (DeviceConfigNotFoundException e) {
Charles Chan92726132018-02-16 17:20:54 -0800352 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
353 continue;
Saurav Das7bcbe702017-06-13 15:35:54 -0700354 }
Charles Chanba6c5752018-04-02 11:46:38 -0700355 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700356 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chanba6c5752018-04-02 11:46:38 -0700357 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700358 continue;
359 }
360 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
361 }
362 }
363
364 if (!redoRouting(routeChanges, edgePairs, subnets)) {
365 log.debug("populateSubnet: populationStatus is ABORTED");
366 populationStatus = Status.ABORTED;
367 log.warn("Failed to repopulate the rules for subnet.");
368 return;
369 }
370
371 log.debug("populateSubnet: populationStatus is SUCCEEDED");
372 populationStatus = Status.SUCCEEDED;
373 log.info("Completed subnet population. Total # of rules pushed : {}",
374 rulePopulator.getCounter());
375 return;
376
377 } finally {
378 statusLock.unlock();
379 }
380 }
381
382 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700383 * Populates the routing rules or makes hash group changes according to the
384 * route-path changes due to link failure, switch failure or link up. This
385 * method should only be called for one of these three possible event-types.
Saurav Das604ab3a2018-03-18 21:28:15 -0700386 * Note that when a switch goes away, all of its links fail as well, but
387 * this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700388 *
Saurav Das604ab3a2018-03-18 21:28:15 -0700389 * @param linkDown the single failed link, or null for other conditions such
390 * as link-up or a removed switch
Saurav Dasc88d4662017-05-15 15:34:25 -0700391 * @param linkUp the single link up, or null for other conditions such as
Saurav Das604ab3a2018-03-18 21:28:15 -0700392 * link-down or a removed switch
393 * @param switchDown the removed switch, or null for other conditions such
394 * as link-down or link-up
395 * @param seenBefore true if this event is for a linkUp or linkDown for a
396 * seen link
397 */
398 // TODO This method should be refactored into three separated methods
Saurav Dasc88d4662017-05-15 15:34:25 -0700399 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
400 Link linkUp,
Saurav Das604ab3a2018-03-18 21:28:15 -0700401 DeviceId switchDown,
402 boolean seenBefore) {
403 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
404 .count() != 1) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700405 log.warn("Only one event can be handled for link status change .. aborting");
406 return;
407 }
Saurav Das604ab3a2018-03-18 21:28:15 -0700408
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700409 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900410 statusLock.lock();
411 try {
sangho20eff1d2015-04-13 15:15:58 -0700412
413 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700414 log.warn("Previous rule population is not finished. Cannot"
Saurav Dasc568c342018-01-25 09:49:01 -0800415 + " proceeed with routingRules for Topology change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700416 return;
sangho20eff1d2015-04-13 15:15:58 -0700417 }
418
Saurav Das7bcbe702017-06-13 15:35:54 -0700419 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700420 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700421 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700422 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530423 EcmpShortestPathGraph ecmpSpgUpdated =
424 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700425 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700426 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
427 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700428 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700429 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
430 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
431 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700432 }
sangho45b009c2015-05-07 13:30:57 -0700433 }
434
Saurav Dasc568c342018-01-25 09:49:01 -0800435 log.info("Starting to populate routing rules from Topology change");
sangho52abe3a2015-05-05 14:13:34 -0700436
sangho20eff1d2015-04-13 15:15:58 -0700437 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700438 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700439 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700440 populationStatus = Status.STARTED;
Saurav Dasc568c342018-01-25 09:49:01 -0800441 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
442 boolean hashGroupsChanged = false;
Saurav Das4e3224f2016-11-29 14:27:25 -0800443 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700444 if (linkDown == null) {
445 // either a linkUp or a switchDown - compute all route changes by
446 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dase0d4c872018-03-05 14:37:16 -0800447 routeChanges = computeRouteChange(switchDown);
Saurav Dasc88d4662017-05-15 15:34:25 -0700448
Saurav Das9df5b7c2017-08-14 16:44:43 -0700449 // deal with linkUp of a seen-before link
Saurav Das604ab3a2018-03-18 21:28:15 -0700450 if (linkUp != null && seenBefore) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700451 // link previously seen before
452 // do hash-bucket changes instead of a re-route
453 processHashGroupChange(routeChanges, false, null);
454 // clear out routesChanges so a re-route is not attempted
455 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800456 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700457 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700458 // for a linkUp of a never-seen-before link
459 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700460
Saurav Das9df5b7c2017-08-14 16:44:43 -0700461 //deal with switchDown
462 if (switchDown != null) {
463 processHashGroupChange(routeChanges, true, switchDown);
464 // clear out routesChanges so a re-route is not attempted
465 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800466 hashGroupsChanged = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700467 }
sangho20eff1d2015-04-13 15:15:58 -0700468 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700469 // link has gone down
470 // Compare existing ECMP SPG only with the link that went down
471 routeChanges = computeDamagedRoutes(linkDown);
472 if (routeChanges != null) {
473 processHashGroupChange(routeChanges, true, null);
474 // clear out routesChanges so a re-route is not attempted
475 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800476 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700477 }
sangho20eff1d2015-04-13 15:15:58 -0700478 }
479
Saurav Das4e3224f2016-11-29 14:27:25 -0800480 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700481 if (routeChanges == null) {
Saurav Dasc568c342018-01-25 09:49:01 -0800482 log.warn("Optimized routing failed... opting for full reroute");
Saurav Das7bcbe702017-06-13 15:35:54 -0700483 populationStatus = Status.ABORTED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700484 populateAllRoutingRules();
485 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700486 }
487
sangho20eff1d2015-04-13 15:15:58 -0700488 if (routeChanges.isEmpty()) {
Saurav Dasc568c342018-01-25 09:49:01 -0800489 if (hashGroupsChanged) {
490 log.info("Hash-groups changed for link status change");
491 } else {
492 log.info("No re-route or re-hash attempted for the link"
493 + " status change");
494 updatedEcmpSpgMap.keySet().forEach(devId -> {
495 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
496 log.debug("Updating ECMPspg for remaining dev:{}", devId);
497 });
498 }
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700499 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700500 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700501 return;
sangho20eff1d2015-04-13 15:15:58 -0700502 }
503
Saurav Dasc88d4662017-05-15 15:34:25 -0700504 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700505 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700506 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700507 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700508 log.info("Completed repopulation of rules for link-status change."
509 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700510 return;
sangho20eff1d2015-04-13 15:15:58 -0700511 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700512 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700513 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700514 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700515 return;
sangho20eff1d2015-04-13 15:15:58 -0700516 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900517 } finally {
518 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700519 }
520 }
521
Saurav Dasc88d4662017-05-15 15:34:25 -0700522 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700523 * Processes a set a route-path changes by reprogramming routing rules and
524 * creating new hash-groups or editing them if necessary. This method also
525 * determines the next-hops for the route-path from the src-switch (target)
526 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700527 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700528 * @param routeChanges a set of route-path changes, where each route-path is
529 * a list with its first element the src-switch (target)
530 * of the path, and the second element the dst-switch of
531 * the path.
532 * @param edgePairs a set of edge-switches that are paired by configuration
533 * @param subnets a set of prefixes that need to be populated in the routing
534 * table of the target switch in the route-path. Can be null,
535 * in which case all the prefixes belonging to the dst-switch
536 * will be populated in the target switch
537 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700538 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700539 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
540 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
541 // first make every entry two-elements
542 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
543 for (ArrayList<DeviceId> route : routeChanges) {
544 if (route.size() == 1) {
545 DeviceId dstSw = route.get(0);
546 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
547 if (ec == null) {
548 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
549 return false;
550 }
551 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
552 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
553 changedRoutes.add(Lists.newArrayList(target, dstSw));
554 });
555 });
556 } else {
557 DeviceId targetSw = route.get(0);
558 DeviceId dstSw = route.get(1);
559 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
560 }
561 }
562
563 // now process changedRoutes according to edgePairs
564 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
565 return false; //abort routing and fail fast
566 }
567
568 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Dasc568c342018-01-25 09:49:01 -0800569 Set<DeviceId> updatedDevices = Sets.newHashSet();
570 if (!redoRoutingIndividualDests(subnets, changedRoutes,
571 updatedDevices)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700572 return false; //abort routing and fail fast
573 }
574
Saurav Das7bcbe702017-06-13 15:35:54 -0700575 // update ecmpSPG for all edge-pairs
576 for (EdgePair ep : edgePairs) {
577 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
578 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
579 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
580 }
Saurav Dasc568c342018-01-25 09:49:01 -0800581
582 // here is where we update all devices not touched by this instance
583 updatedEcmpSpgMap.keySet().stream()
584 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
585 .filter(devId -> !updatedDevices.contains(devId))
586 .forEach(devId -> {
587 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
588 log.debug("Updating ECMPspg for remaining dev:{}", devId);
589 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700590 return true;
591 }
592
593 /**
594 * Programs targetSw in the changedRoutes for given prefixes reachable by
595 * an edgePair. If no prefixes are given, the method will use configured
596 * subnets/prefixes. If some configured subnets belong only to a specific
597 * destination in the edgePair, then the target switch will be programmed
598 * only to that destination.
599 *
600 * @param edgePairs set of edge-pairs for which target will be programmed
601 * @param subnets a set of prefixes that need to be populated in the routing
602 * table of the target switch in the changedRoutes. Can be null,
603 * in which case all the configured prefixes belonging to the
604 * paired switches will be populated in the target switch
605 * @param changedRoutes a set of route-path changes, where each route-path is
606 * a list with its first element the src-switch (target)
607 * of the path, and the second element the dst-switch of
608 * the path.
609 * @return true if successful
610 */
611 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
612 Set<IpPrefix> subnets,
613 Set<ArrayList<DeviceId>> changedRoutes) {
614 for (EdgePair ep : edgePairs) {
615 // temp store for a target's changedRoutes to this edge-pair
616 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
617 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
618 while (i.hasNext()) {
619 ArrayList<DeviceId> route = i.next();
620 DeviceId dstSw = route.get(1);
621 if (ep.includes(dstSw)) {
622 // routeChange for edge pair found
623 // sort by target iff target is edge and remove from changedRoutes
624 DeviceId targetSw = route.get(0);
625 try {
626 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
627 continue;
628 }
629 } catch (DeviceConfigNotFoundException e) {
630 log.warn(e.getMessage() + "aborting redoRouting");
631 return false;
632 }
633 // route is from another edge to this edge-pair
634 if (targetRoutes.containsKey(targetSw)) {
635 targetRoutes.get(targetSw).add(route);
636 } else {
637 Set<ArrayList<DeviceId>> temp = new HashSet<>();
638 temp.add(route);
639 targetRoutes.put(targetSw, temp);
640 }
641 i.remove();
642 }
643 }
644 // so now for this edgepair we have a per target set of routechanges
645 // process target->edgePair route
646 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
647 targetRoutes.entrySet()) {
648 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
649 entry.getKey(), ep);
650 DeviceId targetSw = entry.getKey();
651 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
652 entry.getValue().forEach(route -> {
653 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
654 log.debug("route: target {} -> dst {} found with next-hops {}",
655 route.get(0), route.get(1), nhops);
656 perDstNextHops.put(route.get(1), nhops);
657 });
658 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
659 : subnets;
660 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
661 : subnets;
662 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
663 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Dasc568c342018-01-25 09:49:01 -0800664 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
665 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700666 // handle routing to subnets common to edge-pair
Saurav Dasc568c342018-01-25 09:49:01 -0800667 // only if the targetSw is not part of the edge-pair and there
668 // exists a next hop to at least one of the devices in the edge-pair
669 if (!ep.includes(targetSw)
670 && ((nhDev1 != null && !nhDev1.isEmpty())
671 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700672 if (!populateEcmpRoutingRulePartial(
673 targetSw,
674 ep.dev1, ep.dev2,
675 perDstNextHops,
676 Sets.intersection(ipDev1, ipDev2))) {
677 return false; // abort everything and fail fast
678 }
679 }
Saurav Dasc568c342018-01-25 09:49:01 -0800680 // handle routing to subnets that only belong to dev1 only if
681 // a next-hop exists from the target to dev1
Saurav Das7bcbe702017-06-13 15:35:54 -0700682 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Dasc568c342018-01-25 09:49:01 -0800683 if (!onlyDev1Subnets.isEmpty()
684 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700685 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800686 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das7bcbe702017-06-13 15:35:54 -0700687 if (!populateEcmpRoutingRulePartial(
688 targetSw,
689 ep.dev1, null,
690 onlyDev1NextHops,
691 onlyDev1Subnets)) {
692 return false; // abort everything and fail fast
693 }
694 }
Saurav Dasc568c342018-01-25 09:49:01 -0800695 // handle routing to subnets that only belong to dev2 only if
696 // a next-hop exists from the target to dev2
Saurav Das7bcbe702017-06-13 15:35:54 -0700697 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Dasc568c342018-01-25 09:49:01 -0800698 if (!onlyDev2Subnets.isEmpty()
699 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700700 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800701 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700702 if (!populateEcmpRoutingRulePartial(
703 targetSw,
704 ep.dev2, null,
705 onlyDev2NextHops,
706 onlyDev2Subnets)) {
707 return false; // abort everything and fail fast
708 }
709 }
710 }
711 // if it gets here it has succeeded for all targets to this edge-pair
712 }
713 return true;
714 }
715
716 /**
717 * Programs targetSw in the changedRoutes for given prefixes reachable by
718 * a destination switch that is not part of an edge-pair.
719 * If no prefixes are given, the method will use configured subnets/prefixes.
720 *
721 * @param subnets a set of prefixes that need to be populated in the routing
722 * table of the target switch in the changedRoutes. Can be null,
723 * in which case all the configured prefixes belonging to the
724 * paired switches will be populated in the target switch
725 * @param changedRoutes a set of route-path changes, where each route-path is
726 * a list with its first element the src-switch (target)
727 * of the path, and the second element the dst-switch of
728 * the path.
729 * @return true if successful
730 */
731 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Dasc568c342018-01-25 09:49:01 -0800732 Set<ArrayList<DeviceId>> changedRoutes,
733 Set<DeviceId> updatedDevices) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700734 // aggregate route-path changes for each dst device
735 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
736 new HashMap<>();
737 for (ArrayList<DeviceId> route: changedRoutes) {
738 DeviceId dstSw = route.get(1);
739 ArrayList<ArrayList<DeviceId>> deviceRoutes =
740 routesBydevice.get(dstSw);
741 if (deviceRoutes == null) {
742 deviceRoutes = new ArrayList<>();
743 routesBydevice.put(dstSw, deviceRoutes);
744 }
745 deviceRoutes.add(route);
746 }
747 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
748 ArrayList<ArrayList<DeviceId>> deviceRoutes =
749 routesBydevice.get(impactedDstDevice);
750 for (ArrayList<DeviceId> route: deviceRoutes) {
751 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
752 route.get(0), route.get(1));
753 DeviceId targetSw = route.get(0);
754 DeviceId dstSw = route.get(1); // same as impactedDstDevice
755 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Dasbd071d82018-01-09 17:38:44 -0800756 if (nextHops.isEmpty()) {
757 log.warn("Could not find next hop from target:{} --> dst {} "
758 + "skipping this route", targetSw, dstSw);
759 continue;
760 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700761 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
762 nhops.put(dstSw, nextHops);
763 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
764 (subnets == null) ? Sets.newHashSet() : subnets)) {
765 return false; // abort routing and fail fast
766 }
767 log.debug("Populating flow rules from target: {} to dst: {}"
768 + " is successful", targetSw, dstSw);
769 }
770 //Only if all the flows for all impacted routes to a
771 //specific target are pushed successfully, update the
772 //ECMP graph for that target. Or else the next event
773 //would not see any changes in the ECMP graphs.
774 //In another case, the target switch has gone away, so
775 //routes can't be installed. In that case, the current map
776 //is updated here, without any flows being pushed.
777 currentEcmpSpgMap.put(impactedDstDevice,
778 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Dasc568c342018-01-25 09:49:01 -0800779 updatedDevices.add(impactedDstDevice);
Saurav Das7bcbe702017-06-13 15:35:54 -0700780 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
781 }
782 return true;
783 }
784
785 /**
786 * Populate ECMP rules for subnets from target to destination via nexthops.
787 *
788 * @param targetSw Device ID of target switch in which rules will be programmed
789 * @param destSw1 Device ID of final destination switch to which the rules will forward
790 * @param destSw2 Device ID of paired destination switch to which the rules will forward
791 * A null deviceId indicates packets should only be sent to destSw1
Saurav Dasa4020382018-02-14 14:14:54 -0800792 * @param nextHops Map of a set of next hops per destSw
Saurav Das7bcbe702017-06-13 15:35:54 -0700793 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
794 * @return true if it succeeds in populating rules
795 */ // refactor
796 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
797 DeviceId destSw1,
798 DeviceId destSw2,
799 Map<DeviceId, Set<DeviceId>> nextHops,
800 Set<IpPrefix> subnets) {
801 boolean result;
802 // If both target switch and dest switch are edge routers, then set IP
803 // rule for both subnet and router IP.
804 boolean targetIsEdge;
805 boolean dest1IsEdge;
806 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
807 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
808
809 try {
810 targetIsEdge = config.isEdgeDevice(targetSw);
811 dest1IsEdge = config.isEdgeDevice(destSw1);
812 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
813 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
814 if (destSw2 != null) {
815 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
816 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
817 }
818 } catch (DeviceConfigNotFoundException e) {
819 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700820 return false;
821 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700822
823 if (targetIsEdge && dest1IsEdge) {
824 subnets = (subnets != null && !subnets.isEmpty())
825 ? Sets.newHashSet(subnets)
826 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Dasa4020382018-02-14 14:14:54 -0800827 // XXX - Rethink this - ignoring routerIPs in all other switches
828 // even edge to edge switches
Saurav Das7bcbe702017-06-13 15:35:54 -0700829 /*subnets.add(dest1RouterIpv4.toIpPrefix());
830 if (dest1RouterIpv6 != null) {
831 subnets.add(dest1RouterIpv6.toIpPrefix());
832 }
833 if (destSw2 != null && dest2RouterIpv4 != null) {
834 subnets.add(dest2RouterIpv4.toIpPrefix());
835 if (dest2RouterIpv6 != null) {
836 subnets.add(dest2RouterIpv6.toIpPrefix());
837 }
838 }*/
839 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
840 + "for subnets {}", targetSw, destSw1,
841 (destSw2 != null) ? ("& " + destSw2) : "",
842 subnets);
843 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
844 destSw1, destSw2,
845 nextHops);
846 if (!result) {
847 return false;
848 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700849 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700850
851 if (!targetIsEdge && dest1IsEdge) {
852 // MPLS rules in all non-edge target devices. These rules are for
853 // individual destinations, even if the dsts are part of edge-pairs.
854 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
855 + "all MPLS rules", targetSw, destSw1);
856 result = rulePopulator.populateMplsRule(targetSw, destSw1,
857 nextHops.get(destSw1),
858 dest1RouterIpv4);
859 if (!result) {
860 return false;
861 }
862 if (dest1RouterIpv6 != null) {
Saurav Dasa4020382018-02-14 14:14:54 -0800863 int v4sid = 0, v6sid = 0;
864 try {
865 v4sid = config.getIPv4SegmentId(destSw1);
866 v6sid = config.getIPv6SegmentId(destSw1);
867 } catch (DeviceConfigNotFoundException e) {
868 log.warn(e.getMessage());
869 }
870 if (v4sid != v6sid) {
871 result = rulePopulator.populateMplsRule(targetSw, destSw1,
872 nextHops.get(destSw1),
873 dest1RouterIpv6);
874 if (!result) {
875 return false;
876 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700877 }
878 }
879 }
880
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -0700881 if (!targetIsEdge && !dest1IsEdge) {
882 // MPLS rules for inter-connected spines
883 // can be merged with above if, left it here for clarity
884 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
885 + "all MPLS rules", targetSw, destSw1);
886
887 result = rulePopulator.populateMplsRule(targetSw, destSw1,
888 nextHops.get(destSw1),
889 dest1RouterIpv4);
890 if (!result) {
891 return false;
892 }
893
894 if (dest1RouterIpv6 != null) {
895 int v4sid = 0, v6sid = 0;
896 try {
897 v4sid = config.getIPv4SegmentId(destSw1);
898 v6sid = config.getIPv6SegmentId(destSw1);
899 } catch (DeviceConfigNotFoundException e) {
900 log.warn(e.getMessage());
901 }
902 if (v4sid != v6sid) {
903 result = rulePopulator.populateMplsRule(targetSw, destSw1,
904 nextHops.get(destSw1),
905 dest1RouterIpv6);
906 if (!result) {
907 return false;
908 }
909 }
910 }
911 }
912
913
Saurav Das7bcbe702017-06-13 15:35:54 -0700914 // To save on ECMP groups
915 // avoid MPLS rules in non-edge-devices to non-edge-devices
916 // avoid MPLS transit rules in edge-devices
917 // avoid loopback IP rules in edge-devices to non-edge-devices
918 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700919 }
920
921 /**
922 * Processes a set a route-path changes by editing hash groups.
923 *
924 * @param routeChanges a set of route-path changes, where each route-path is
925 * a list with its first element the src-switch of the path
926 * and the second element the dst-switch of the path.
927 * @param linkOrSwitchFailed true if the route changes are for a failed
928 * switch or linkDown event
929 * @param failedSwitch the switchId if the route changes are for a failed switch,
930 * otherwise null
931 */
932 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
933 boolean linkOrSwitchFailed,
934 DeviceId failedSwitch) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700935 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
936 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700937 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700938 if (route.size() == 1) {
939 // route-path changes are from everyone else to this switch
940 DeviceId dstSw = route.get(0);
941 srManager.deviceService.getAvailableDevices().forEach(sw -> {
942 if (!sw.id().equals(dstSw)) {
943 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
944 }
945 });
946 } else {
947 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700948 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700949 }
Saurav Dasc568c342018-01-25 09:49:01 -0800950 boolean someFailed = false;
951 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Das9df5b7c2017-08-14 16:44:43 -0700952 for (ArrayList<DeviceId> route : changedRoutes) {
953 DeviceId targetSw = route.get(0);
954 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700955 if (linkOrSwitchFailed) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700956 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700957 // it's possible that we cannot fix hash groups for a route
958 // if the target switch has failed. Nevertheless the ecmp graph
959 // for the impacted switch must still be updated.
Saurav Das9df5b7c2017-08-14 16:44:43 -0700960 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700961 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
962 currentEcmpSpgMap.remove(targetSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700963 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -0700964 + "target:{}", dstSw, targetSw);
Saurav Dasc568c342018-01-25 09:49:01 -0800965 updatedDevices.add(targetSw);
966 updatedDevices.add(dstSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700967 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -0700968 }
969 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -0700970 if (success) {
971 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das9df5b7c2017-08-14 16:44:43 -0700972 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Dasc568c342018-01-25 09:49:01 -0800973 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
974 + " or switchdown", dstSw, targetSw);
975 updatedDevices.add(targetSw);
976 updatedDevices.add(dstSw);
977 } else {
978 someFailed = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700979 }
980 } else {
981 //linkup of seen before link
982 boolean success = fixHashGroupsForRoute(route, false);
983 if (success) {
984 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
985 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
986 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -0700987 targetSw, dstSw);
Saurav Dasc568c342018-01-25 09:49:01 -0800988 updatedDevices.add(targetSw);
989 updatedDevices.add(dstSw);
990 } else {
991 someFailed = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700992 }
993 }
994 }
Saurav Dasc568c342018-01-25 09:49:01 -0800995 if (!someFailed) {
996 // here is where we update all devices not touched by this instance
997 updatedEcmpSpgMap.keySet().stream()
998 .filter(devId -> !updatedDevices.contains(devId))
999 .forEach(devId -> {
1000 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1001 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1002 });
1003 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001004 }
1005
1006 /**
1007 * Edits hash groups in the src-switch (targetSw) of a route-path by
1008 * calling the groupHandler to either add or remove buckets in an existing
1009 * hash group.
1010 *
1011 * @param route a single list representing a route-path where the first element
1012 * is the src-switch (targetSw) of the route-path and the
1013 * second element is the dst-switch
1014 * @param revoke true if buckets in the hash-groups need to be removed;
1015 * false if buckets in the hash-groups need to be added
1016 * @return true if the hash group editing is successful
1017 */
1018 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1019 boolean revoke) {
1020 DeviceId targetSw = route.get(0);
1021 if (route.size() < 2) {
1022 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1023 return false;
1024 }
1025 DeviceId destSw = route.get(1);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001026 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001027 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001028 // figure out the new next hops at the targetSw towards the destSw
Saurav Das9df5b7c2017-08-14 16:44:43 -07001029 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001030 // call group handler to change hash group at targetSw
1031 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1032 if (grpHandler == null) {
1033 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1034 + " {} hash group buckets for route:{} ", targetSw,
1035 (revoke) ? "revoke" : "repopulate", route);
1036 return false;
1037 }
1038 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
1039 (revoke) ? "revoke" : "repopulating",
1040 targetSw, destSw, nextHops);
1041 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1042 destSw, true)
1043 : grpHandler.fixHashGroups(targetSw, nextHops,
1044 destSw, false);
1045 }
1046
1047 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001048 * Start the flow rule population process if it was never started. The
1049 * process finishes successfully when all flow rules are set and stops with
1050 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -07001051 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001052 public void startPopulationProcess() {
1053 statusLock.lock();
1054 try {
1055 if (populationStatus == Status.IDLE
1056 || populationStatus == Status.SUCCEEDED
1057 || populationStatus == Status.ABORTED) {
1058 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -07001059 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -07001060 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1061 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001062 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001063 } finally {
1064 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001065 }
sangho20eff1d2015-04-13 15:15:58 -07001066 }
1067
Saurav Dasb5c236e2016-06-07 10:08:06 -07001068 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001069 * Revoke rules of given subnet in all edge switches.
1070 *
1071 * @param subnets subnet being removed
1072 * @return true if succeed
1073 */
1074 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1075 statusLock.lock();
1076 try {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001077 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1078 .map(Device::id)
1079 .filter(this::shouldProgram)
1080 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das7bcbe702017-06-13 15:35:54 -07001081 } finally {
1082 statusLock.unlock();
1083 }
1084 }
1085
1086 /**
Charles Chan2fde6d42017-08-23 14:46:43 -07001087 * Populates IP rules for a route that has direct connection to the switch
1088 * if the current instance is the master of the switch.
1089 *
1090 * @param deviceId device ID of the device that next hop attaches to
1091 * @param prefix IP prefix of the route
1092 * @param hostMac MAC address of the next hop
1093 * @param hostVlanId Vlan ID of the nexthop
1094 * @param outPort port where the next hop attaches to
1095 */
1096 void populateRoute(DeviceId deviceId, IpPrefix prefix,
1097 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001098 if (shouldProgram(deviceId)) {
Charles Chan2fde6d42017-08-23 14:46:43 -07001099 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1100 }
1101 }
1102
1103 /**
1104 * Removes IP rules for a route when the next hop is gone.
1105 * if the current instance is the master of the switch.
1106 *
1107 * @param deviceId device ID of the device that next hop attaches to
1108 * @param prefix IP prefix of the route
1109 * @param hostMac MAC address of the next hop
1110 * @param hostVlanId Vlan ID of the nexthop
1111 * @param outPort port that next hop attaches to
1112 */
1113 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1114 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001115 if (shouldProgram(deviceId)) {
Charles Chan2fde6d42017-08-23 14:46:43 -07001116 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1117 }
1118 }
1119
Charles Chan2ff1bac2018-03-29 16:03:41 -07001120 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1121 if (shouldProgram(deviceId)) {
1122 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1123 }
1124 }
1125
1126 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1127 if (shouldProgram(deviceId)) {
1128 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1129 }
1130 }
1131
1132 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1133 VlanId vlanId, boolean popVlan, boolean install) {
1134 if (shouldProgram(deviceId)) {
1135 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1136 }
1137 }
1138
1139 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1140 VlanId vlanId, boolean popVlan, boolean install) {
1141 if (shouldProgram(deviceId)) {
1142 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1143 vlanId, popVlan, install);
1144 }
1145 }
1146
Charles Chan2fde6d42017-08-23 14:46:43 -07001147 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001148 * Remove ECMP graph entry for the given device. Typically called when
1149 * device is no longer available.
1150 *
1151 * @param deviceId the device for which graphs need to be purged
1152 */
1153 protected void purgeEcmpGraph(DeviceId deviceId) {
Saurav Dasc568c342018-01-25 09:49:01 -08001154 statusLock.lock();
1155 try {
1156
1157 if (populationStatus == Status.STARTED) {
1158 log.warn("Previous rule population is not finished. Cannot"
1159 + " proceeed with purgeEcmpGraph for {}", deviceId);
1160 return;
1161 }
1162 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1163 currentEcmpSpgMap.remove(deviceId);
1164 if (updatedEcmpSpgMap != null) {
1165 updatedEcmpSpgMap.remove(deviceId);
1166 }
1167 } finally {
1168 statusLock.unlock();
Saurav Das7bcbe702017-06-13 15:35:54 -07001169 }
1170 }
1171
1172 //////////////////////////////////////
1173 // Routing helper methods and classes
1174 //////////////////////////////////////
1175
1176 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001177 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001178 * previous ecmp shortest-path graph exists for a switch in order to compute
1179 * affected routes. If such a graph does not exist, the method returns null.
1180 *
1181 * @param linkFail the failed link
1182 * @return the set of affected routes which may be empty if no routes were
1183 * affected, or null if no previous ecmp spg was found for comparison
1184 */
sangho20eff1d2015-04-13 15:15:58 -07001185 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001186 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1187
1188 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001189 log.debug("Computing the impacted routes for device {} due to link fail",
1190 sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001191 if (!shouldProgram(sw.id())) {
sangho20eff1d2015-04-13 15:15:58 -07001192 continue;
1193 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001194 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das9df5b7c2017-08-14 16:44:43 -07001195 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1196 if (ecmpSpg == null) {
1197 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1198 + " rerouting and opting for full-reroute", rootSw);
1199 return null;
1200 }
1201 if (log.isDebugEnabled()) {
1202 log.debug("Root switch: {}", rootSw);
1203 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1204 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1205 }
1206 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1207 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1208 // figure out if the broken link affected any route-paths in this graph
1209 for (Integer itrIdx : switchVia.keySet()) {
1210 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1211 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1212 switchVia.get(itrIdx);
1213 for (DeviceId targetSw : swViaMap.keySet()) {
1214 log.trace("TargetSwitch {} --> RootSwitch {}",
1215 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001216 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1217 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001218 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001219 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001220 Set<ArrayList<DeviceId>> subLinks =
1221 computeLinks(targetSw, rootSw, swViaMap);
1222 for (ArrayList<DeviceId> alink: subLinks) {
1223 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1224 alink.get(1).equals(linkFail.dst().deviceId()))
1225 ||
1226 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1227 alink.get(1).equals(linkFail.src().deviceId()))) {
1228 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1229 ArrayList<DeviceId> aRoute = new ArrayList<>();
1230 aRoute.add(targetSw); // switch with rules to populate
1231 aRoute.add(rootSw); // towards this destination
1232 routes.add(aRoute);
1233 break;
1234 }
sangho20eff1d2015-04-13 15:15:58 -07001235 }
1236 }
1237 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001238
sangho20eff1d2015-04-13 15:15:58 -07001239 }
sangho45b009c2015-05-07 13:30:57 -07001240
sangho20eff1d2015-04-13 15:15:58 -07001241 }
sangho20eff1d2015-04-13 15:15:58 -07001242 return routes;
1243 }
1244
Saurav Das4e3224f2016-11-29 14:27:25 -08001245 /**
1246 * Computes set of affected routes due to new links or failed switches.
1247 *
Saurav Das604ab3a2018-03-18 21:28:15 -07001248 * @param failedSwitch deviceId of failed switch if any
Saurav Das4e3224f2016-11-29 14:27:25 -08001249 * @return the set of affected routes which may be empty if no routes were
1250 * affected
1251 */
Saurav Dase0d4c872018-03-05 14:37:16 -08001252 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001253 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001254 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001255
1256 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001257 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001258 if (!shouldProgram(sw.id())) {
sangho20eff1d2015-04-13 15:15:58 -07001259 continue;
1260 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001261 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001262 if (log.isTraceEnabled()) {
1263 log.trace("Device links for dev: {}", rootSw);
1264 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1265 log.trace("{} -> {} ", link.src().deviceId(),
1266 link.dst().deviceId());
1267 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001268 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001269 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1270 if (currEcmpSpg == null) {
1271 log.debug("No existing ECMP graph for device {}.. adding self as "
1272 + "changed route", rootSw);
1273 changedRtBldr.add(Lists.newArrayList(rootSw));
1274 continue;
1275 }
1276 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Das5a356042018-04-06 20:16:01 -07001277 if (newEcmpSpg == null) {
1278 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1279 continue;
1280 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001281 if (log.isDebugEnabled()) {
1282 log.debug("Root switch: {}", rootSw);
1283 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1284 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1285 }
1286 // first use the updated/new map to compare to current/existing map
1287 // as new links may have come up
1288 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1289 // then use the current/existing map to compare to updated/new map
1290 // as switch may have been removed
1291 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001292 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001293 }
sangho20eff1d2015-04-13 15:15:58 -07001294
Saurav Dase0d4c872018-03-05 14:37:16 -08001295 // handle clearing state for a failed switch in case the switch does
1296 // not have a pair, or the pair is not available
1297 if (failedSwitch != null) {
Charles Chanba6c5752018-04-02 11:46:38 -07001298 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1299 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dase0d4c872018-03-05 14:37:16 -08001300 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1301 srManager.deviceService.getDevices().forEach(dev -> {
1302 if (!dev.id().equals(failedSwitch) &&
1303 srManager.mastershipService.isLocalMaster(dev.id())) {
1304 log.debug(" : {}", dev.id());
1305 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1306 }
1307 });
1308 }
1309 }
1310
Saurav Das7bcbe702017-06-13 15:35:54 -07001311 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001312 for (ArrayList<DeviceId> route: changedRoutes) {
1313 log.debug("Route changes Target -> Root");
1314 if (route.size() == 1) {
1315 log.debug(" : all -> {}", route.get(0));
1316 } else {
1317 log.debug(" : {} -> {}", route.get(0), route.get(1));
1318 }
1319 }
1320 return changedRoutes;
1321 }
1322
1323 /**
1324 * For the root switch, searches all the target nodes reachable in the base
1325 * graph, and compares paths to the ones in the comp graph.
1326 *
1327 * @param base the graph that is indexed for all reachable target nodes
1328 * from the root node
1329 * @param comp the graph that the base graph is compared to
1330 * @param rootSw both ecmp graphs are calculated for the root node
1331 * @return all the routes that have changed in the base graph
1332 */
1333 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1334 EcmpShortestPathGraph comp,
1335 DeviceId rootSw) {
1336 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1337 ImmutableSet.builder();
1338 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1339 base.getAllLearnedSwitchesAndVia();
1340 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1341 comp.getAllLearnedSwitchesAndVia();
1342 for (Integer itrIdx : baseMap.keySet()) {
1343 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1344 baseMap.get(itrIdx);
1345 for (DeviceId targetSw : baseViaMap.keySet()) {
1346 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1347 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1348 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001349 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001350 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001351 route.add(targetSw); // switch with rules to populate
1352 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001353 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001354 }
1355 }
sangho45b009c2015-05-07 13:30:57 -07001356 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001357 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001358 }
1359
Saurav Das7bcbe702017-06-13 15:35:54 -07001360 /**
1361 * Returns the ECMP paths traversed to reach the target switch.
1362 *
1363 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1364 * @param targetSw the switch to reach from the root switch
1365 * @return the nodes traversed on ECMP paths to the target switch
1366 */
sangho20eff1d2015-04-13 15:15:58 -07001367 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001368 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001369 for (Integer itrIdx : switchVia.keySet()) {
1370 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1371 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001372 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001373 continue;
1374 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001375 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001376 }
1377 }
1378
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001379 return null;
sangho20eff1d2015-04-13 15:15:58 -07001380 }
1381
Saurav Das7bcbe702017-06-13 15:35:54 -07001382 /**
1383 * Utility method to break down a path from src to dst device into a collection
1384 * of links.
1385 *
1386 * @param src src device of the path
1387 * @param dst dst device of the path
1388 * @param viaMap path taken from src to dst device
1389 * @return collection of links in the path
1390 */
sangho20eff1d2015-04-13 15:15:58 -07001391 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1392 DeviceId dst,
1393 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1394 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1395 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1396 DeviceId linkSrc = src;
1397 DeviceId linkDst = dst;
1398 for (DeviceId viaDevice: via) {
1399 ArrayList<DeviceId> link = new ArrayList<>();
1400 linkDst = viaDevice;
1401 link.add(linkSrc);
1402 link.add(linkDst);
1403 subLinks.add(link);
1404 linkSrc = viaDevice;
1405 }
1406 ArrayList<DeviceId> link = new ArrayList<>();
1407 link.add(linkSrc);
1408 link.add(dst);
1409 subLinks.add(link);
1410 }
1411
1412 return subLinks;
1413 }
1414
Charles Chan93e71ba2016-04-29 14:38:22 -07001415 /**
Charles Chan2ff1bac2018-03-29 16:03:41 -07001416 * Determines whether this controller instance should program the
Saurav Das7bcbe702017-06-13 15:35:54 -07001417 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chan2ff1bac2018-03-29 16:03:41 -07001418 * <p>
1419 * Once an instance is elected, it will be the only instance responsible for programming
1420 * both devices in the pair until it goes down.
Charles Chan93e71ba2016-04-29 14:38:22 -07001421 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001422 * @param deviceId device identifier to consider for routing
Charles Chan2ff1bac2018-03-29 16:03:41 -07001423 * @return true if current instance should handle the routing for given device
Charles Chan93e71ba2016-04-29 14:38:22 -07001424 */
Charles Chan2ff1bac2018-03-29 16:03:41 -07001425 boolean shouldProgram(DeviceId deviceId) {
1426 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001427
Charles Chan2ff1bac2018-03-29 16:03:41 -07001428 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1429 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1430 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
1431 log.debug("Evaluate shouldProgram {}/pair={}. current={}, master={}, pairMaster={}",
1432 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1433
1434 // No pair device configured. Only handle when current instance is the master of the device
1435 if (!pairDeviceId.isPresent()) {
1436 log.debug("No pair device. current={}, master={}", currentNodeId, masterNodeId);
1437 return currentNodeId.equals(masterNodeId);
sanghob35a6192015-04-01 13:05:26 -07001438 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001439
1440 // Should not handle if current instance is not the master of either switch
1441 if (!currentNodeId.equals(masterNodeId) &&
1442 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
1443 log.debug("Current node {} is neither the master of target device {} nor pair device {}",
1444 currentNodeId, deviceId, pairDeviceId);
1445 return false;
1446 }
1447
1448 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1449
1450 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1451 if (v == null) {
1452 // There is no value in the map. Elect a node
1453 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1454 } else {
1455 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1456 // Use the node in the map if it is still alive and is a master of any of the two switches
1457 return v;
1458 } else {
1459 // Previously elected node is no longer the master of either switch. Re-elect a node.
1460 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1461 }
1462 }
1463 }));
1464
1465 if (king != null) {
1466 log.debug("{} should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
1467 return king.equals(currentNodeId);
1468 } else {
1469 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
1470 return false;
1471 }
1472 }
1473
1474 /**
1475 * Elects a node who should take responsibility of programming devices.
1476 * @param nodeIds list of candidate node ID
1477 *
1478 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1479 */
1480 private NodeId elect(List<NodeId> nodeIds) {
1481 // Remove all null elements. This could happen when some device has no master
1482 nodeIds.removeAll(Collections.singleton(null));
1483 nodeIds.sort(null);
1484 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1485 }
1486
1487 /**
1488 * Returns a set of device ID, containing given device and its pair device if exist.
1489 *
1490 * @param deviceId Device ID
1491 * @return a set of device ID, containing given device and its pair device if exist.
1492 */
1493 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1494 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1495 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1496 return ret;
sanghob35a6192015-04-01 13:05:26 -07001497 }
1498
Charles Chan93e71ba2016-04-29 14:38:22 -07001499 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001500 * Returns the set of deviceIds which are the next hops from the targetSw
1501 * to the dstSw according to the latest ECMP spg.
1502 *
1503 * @param targetSw the switch for which the next-hops are desired
1504 * @param dstSw the switch to which the next-hops lead to from the targetSw
1505 * @return set of next hop deviceIds, could be empty if no next hops are found
1506 */
1507 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1508 boolean targetIsEdge = false;
1509 try {
1510 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1511 } catch (DeviceConfigNotFoundException e) {
1512 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1513 + "continuing to getNextHops", targetSw);
1514 }
1515
1516 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1517 if (ecmpSpg == null) {
1518 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1519 return ImmutableSet.of();
1520 }
1521 HashMap<Integer,
1522 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1523 ecmpSpg.getAllLearnedSwitchesAndVia();
1524 for (Integer itrIdx : switchVia.keySet()) {
1525 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1526 switchVia.get(itrIdx);
1527 for (DeviceId target : swViaMap.keySet()) {
1528 if (!target.equals(targetSw)) {
1529 continue;
1530 }
1531 if (!targetIsEdge && itrIdx > 1) {
Saurav Dasa4020382018-02-14 14:14:54 -08001532 // optimization for spines to not use leaves to get
1533 // to a spine or other leaves
1534 boolean pathdevIsEdge = false;
1535 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1536 for (DeviceId pathdev : via) {
1537 try {
1538 pathdevIsEdge = srManager.deviceConfiguration
1539 .isEdgeDevice(pathdev);
1540 } catch (DeviceConfigNotFoundException e) {
1541 log.warn(e.getMessage());
1542 }
1543 if (pathdevIsEdge) {
1544 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1545 + " --> dstSw:{} which goes through an edge"
1546 + " device {} in path {}", itrIdx,
1547 targetSw, dstSw, pathdev, via);
1548 return ImmutableSet.of();
1549 }
1550 }
1551 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001552 }
1553 Set<DeviceId> nextHops = new HashSet<>();
1554 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1555 if (via.isEmpty()) {
1556 // the dstSw is the next-hop from the targetSw
1557 nextHops.add(dstSw);
1558 } else {
1559 // first elem is next-hop in each ECMP path
1560 nextHops.add(via.get(0));
1561 }
1562 }
1563 return nextHops;
1564 }
1565 }
1566 return ImmutableSet.of(); //no next-hops found
1567 }
1568
1569 /**
1570 * Represents two devices that are paired by configuration. An EdgePair for
1571 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1572 */
1573 protected final class EdgePair {
1574 DeviceId dev1;
1575 DeviceId dev2;
1576
1577 EdgePair(DeviceId dev1, DeviceId dev2) {
1578 this.dev1 = dev1;
1579 this.dev2 = dev2;
1580 }
1581
1582 boolean includes(DeviceId dev) {
1583 return dev1.equals(dev) || dev2.equals(dev);
1584 }
1585
1586 @Override
1587 public boolean equals(Object o) {
1588 if (this == o) {
1589 return true;
1590 }
1591 if (!(o instanceof EdgePair)) {
1592 return false;
1593 }
1594 EdgePair that = (EdgePair) o;
1595 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1596 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1597 }
1598
1599 @Override
1600 public int hashCode() {
1601 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1602 return Objects.hash(dev1, dev2);
1603 } else {
1604 return Objects.hash(dev2, dev1);
1605 }
1606 }
1607
1608 @Override
1609 public String toString() {
1610 return toStringHelper(this)
1611 .add("Dev1", dev1)
1612 .add("Dev2", dev2)
1613 .toString();
1614 }
1615 }
1616
1617 //////////////////////////////////////
1618 // Filtering rule creation
1619 //////////////////////////////////////
1620
1621 /**
Saurav Das018605f2017-02-18 14:05:44 -08001622 * Populates filtering rules for port, and punting rules
1623 * for gateway IPs, loopback IPs and arp/ndp traffic.
1624 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001625 *
1626 * @param deviceId Switch ID to set the rules
1627 */
Saurav Das822c4e22015-10-23 10:51:11 -07001628 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001629 // Although device is added, sometimes device store does not have the
1630 // ports for this device yet. It results in missing filtering rules in the
1631 // switch. We will attempt it a few times. If it still does not work,
1632 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001633 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001634 if (firstRun == null) {
1635 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001636 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001637 executorService.schedule(new RetryFilters(deviceId, firstRun),
1638 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001639 }
1640
1641 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001642 * Utility class used to temporarily store information about the ports on a
1643 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001644 */
1645 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001646 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001647
Saurav Das018605f2017-02-18 14:05:44 -08001648 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001649 int filteredPorts) {
1650 this.disabledPorts = disabledPorts;
1651 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001652 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001653 }
1654
1655 @Override
1656 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001657 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001658 }
1659
1660 @Override
1661 public boolean equals(Object obj) {
1662 if (this == obj) {
1663 return true;
1664 }
1665 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1666 return false;
1667 }
1668 PortFilterInfo other = (PortFilterInfo) obj;
1669 return ((disabledPorts == other.disabledPorts) &&
1670 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001671 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001672 }
1673
1674 @Override
1675 public String toString() {
1676 MoreObjects.ToStringHelper helper = toStringHelper(this)
1677 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001678 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001679 .add("filteredPorts", filteredPorts);
1680 return helper.toString();
1681 }
1682 }
1683
1684 /**
1685 * RetryFilters populates filtering objectives for a device and keeps retrying
1686 * till the number of ports filtered are constant for a predefined number
1687 * of attempts.
1688 */
1689 protected final class RetryFilters implements Runnable {
1690 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1691 DeviceId devId;
1692 int counter;
1693 PortFilterInfo prevRun;
1694
1695 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001696 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001697 prevRun = previousRun;
1698 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001699 }
1700
1701 @Override
1702 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001703 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001704 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001705 boolean sameResult = prevRun.equals(thisRun);
1706 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1707 thisRun, sameResult);
Ray Milkeyc6c9b172018-02-26 09:36:31 -08001708 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001709 // exponentially increasing intervals for retries
1710 executorService.schedule(this,
1711 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1712 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001713 if (!sameResult) {
1714 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1715 }
Saurav Das59232cf2016-04-27 18:35:50 -07001716 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001717 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001718 }
Saurav Das59232cf2016-04-27 18:35:50 -07001719 }
1720
sanghob35a6192015-04-01 13:05:26 -07001721}