blob: 7503ba8282e8e44f8f2f718d517167efa58fc7a8 [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;
Charles Chan50bb6ef2018-04-18 18:41:05 -070092 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chan2ff1bac2018-03-29 16:03:41 -070093
sanghob35a6192015-04-01 13:05:26 -070094 /**
95 * Represents the default routing population status.
96 */
97 public enum Status {
98 // population process is not started yet.
99 IDLE,
100
101 // population process started.
102 STARTED,
103
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700104 // population process was aborted due to errors, mostly for groups not
105 // found.
sanghob35a6192015-04-01 13:05:26 -0700106 ABORTED,
107
108 // population process was finished successfully.
109 SUCCEEDED
110 }
111
112 /**
113 * Creates a DefaultRoutingHandler object.
114 *
115 * @param srManager SegmentRoutingManager object
116 */
Charles Chan2ff1bac2018-03-29 16:03:41 -0700117 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700118 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
119 .withName("sr-should-program")
120 .withSerializer(Serializer.using(KryoNamespaces.API))
121 .withRelaxedReadConsistency()
122 .build().asJavaMap();
123 this.shouldProgramCache = Maps.newConcurrentMap();
124 update(srManager);
125 }
126
127 /**
128 * Updates a DefaultRoutingHandler object.
129 *
130 * @param srManager SegmentRoutingManager object
131 */
132 void update(SegmentRoutingManager srManager) {
sanghob35a6192015-04-01 13:05:26 -0700133 this.srManager = srManager;
134 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700135 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700136 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700137 this.currentEcmpSpgMap = Maps.newHashMap();
sanghob35a6192015-04-01 13:05:26 -0700138 }
139
140 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700141 * Returns an immutable copy of the current ECMP shortest-path graph as
142 * computed by this controller instance.
143 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700144 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700145 */
146 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
147 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
148 currentEcmpSpgMap.entrySet().forEach(entry -> {
149 if (entry.getValue() != null) {
150 builder.put(entry.getKey(), entry.getValue());
151 }
152 });
153 return builder.build();
154 }
155
Saurav Dasceccf242017-08-03 18:30:35 -0700156 /**
157 * Acquires the lock used when making routing changes.
158 */
159 public void acquireRoutingLock() {
160 statusLock.lock();
161 }
162
163 /**
164 * Releases the lock used when making routing changes.
165 */
166 public void releaseRoutingLock() {
167 statusLock.unlock();
168 }
169
170 /**
171 * Determines if routing in the network has been stable in the last
172 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
173 * routing change timestamp.
174 *
175 * @return true if stable
176 */
177 public boolean isRoutingStable() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700178 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
179 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700180 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700181 return (now - last) > STABLITY_THRESHOLD;
182 }
183
184
Saurav Das7bcbe702017-06-13 15:35:54 -0700185 //////////////////////////////////////
186 // Route path handling
187 //////////////////////////////////////
188
Saurav Das45f48152018-01-18 12:07:33 -0800189 /* The following three methods represent the three major ways in which
190 * route-path handling is triggered in the network
Saurav Das7bcbe702017-06-13 15:35:54 -0700191 * a) due to configuration change
192 * b) due to route-added event
193 * c) due to change in the topology
194 */
195
Saurav Dasc88d4662017-05-15 15:34:25 -0700196 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700197 * Populates all routing rules to all switches. Typically triggered at
198 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700199 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700200 public void populateAllRoutingRules() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700201 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900202 statusLock.lock();
203 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700204 if (populationStatus == Status.STARTED) {
205 log.warn("Previous rule population is not finished. Cannot"
206 + " proceed with populateAllRoutingRules");
207 return;
208 }
209
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900210 populationStatus = Status.STARTED;
211 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700212 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900213 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700214
Saurav Das7bcbe702017-06-13 15:35:54 -0700215 // take a snapshot of the topology
216 updatedEcmpSpgMap = new HashMap<>();
217 Set<EdgePair> edgePairs = new HashSet<>();
218 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800219 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700220 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800221 new EcmpShortestPathGraph(dstSw, srManager);
222 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700223 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
224 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700225 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700226 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
227 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
228 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700229 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700230
231 if (!shouldProgram(dstSw)) {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900232 continue;
233 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700234 // To do a full reroute, assume all routes have changed
Charles Chan2ff1bac2018-03-29 16:03:41 -0700235 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800236 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
237 if (targetSw.equals(dev)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700238 continue;
239 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800240 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700241 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900242 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700243 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900244
Saurav Das7bcbe702017-06-13 15:35:54 -0700245 if (!redoRouting(routeChanges, edgePairs, null)) {
246 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
247 populationStatus = Status.ABORTED;
248 log.warn("Failed to repopulate all routing rules.");
249 return;
sanghob35a6192015-04-01 13:05:26 -0700250 }
251
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900252 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
253 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700254 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900255 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700256 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900257 } finally {
258 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700259 }
sanghob35a6192015-04-01 13:05:26 -0700260 }
261
sangho20eff1d2015-04-13 15:15:58 -0700262 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700263 * Populate rules from all other edge devices to the connect-point(s)
264 * specified for the given subnets.
265 *
266 * @param cpts connect point(s) of the subnets being added
267 * @param subnets subnets being added
Charles Chan2fde6d42017-08-23 14:46:43 -0700268 */
269 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700270 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan71e64f12017-09-11 15:21:57 -0700271 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
272 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
273 return;
274 }
275
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700276 lastRoutingChange = Instant.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700277 statusLock.lock();
278 try {
279 if (populationStatus == Status.STARTED) {
280 log.warn("Previous rule population is not finished. Cannot"
281 + " proceed with routing rules for added routes");
282 return;
283 }
284 populationStatus = Status.STARTED;
285 rulePopulator.resetCounter();
Charles Chan2fde6d42017-08-23 14:46:43 -0700286 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
287 subnets, cpts);
Saurav Dasc568c342018-01-25 09:49:01 -0800288 // In principle an update to a subnet/prefix should not require a
289 // new ECMPspg calculation as it is not a topology event. As a
290 // result, we use the current/existing ECMPspg in the updated map
291 // used by the redoRouting method.
Saurav Das15a81782018-02-09 09:15:03 -0800292 if (updatedEcmpSpgMap == null) {
293 updatedEcmpSpgMap = new HashMap<>();
294 }
Saurav Dasc568c342018-01-25 09:49:01 -0800295 currentEcmpSpgMap.entrySet().forEach(entry -> {
296 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase7f51012018-02-09 17:26:45 -0800297 if (log.isTraceEnabled()) {
298 log.trace("Root switch: {}", entry.getKey());
299 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Dasc568c342018-01-25 09:49:01 -0800300 }
301 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700302 Set<EdgePair> edgePairs = new HashSet<>();
303 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
304 boolean handleRouting = false;
305
306 if (cpts.size() == 2) {
307 // ensure connect points are edge-pairs
308 Iterator<ConnectPoint> iter = cpts.iterator();
309 DeviceId dev1 = iter.next().deviceId();
Charles Chanba6c5752018-04-02 11:46:38 -0700310 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
311 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
312 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700313 } else {
314 log.warn("Connectpoints {} for subnets {} not on "
315 + "pair-devices.. aborting populateSubnet", cpts, subnets);
316 populationStatus = Status.ABORTED;
317 return;
318 }
319 for (ConnectPoint cp : cpts) {
Saurav Dasc568c342018-01-25 09:49:01 -0800320 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
321 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700322 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800323 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
324 log.warn("populateSubnet: no updated graph for dev:{}"
325 + " ... creating", cp.deviceId());
326 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700327 if (!shouldProgram(cp.deviceId())) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700328 continue;
329 }
330 handleRouting = true;
331 }
332 } else {
333 // single connect point
334 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Dasc568c342018-01-25 09:49:01 -0800335 if (updatedEcmpSpgMap.get(dstSw) == null) {
336 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700337 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800338 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
339 log.warn("populateSubnet: no updated graph for dev:{}"
340 + " ... creating", dstSw);
341 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700342 handleRouting = shouldProgram(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700343 }
344
345 if (!handleRouting) {
346 log.debug("This instance is not handling ecmp routing to the "
347 + "connectPoint(s) {}", cpts);
348 populationStatus = Status.ABORTED;
349 return;
350 }
351
352 // if it gets here, this instance should handle routing for the
353 // connectpoint(s). Assume all route-paths have to be updated to
354 // the connectpoint(s) with the following exceptions
355 // 1. if target is non-edge no need for routing rules
356 // 2. if target is one of the connectpoints
357 for (ConnectPoint cp : cpts) {
358 DeviceId dstSw = cp.deviceId();
359 for (Device targetSw : srManager.deviceService.getDevices()) {
360 boolean isEdge = false;
361 try {
362 isEdge = config.isEdgeDevice(targetSw.id());
363 } catch (DeviceConfigNotFoundException e) {
Charles Chan92726132018-02-16 17:20:54 -0800364 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
365 continue;
Saurav Das7bcbe702017-06-13 15:35:54 -0700366 }
Charles Chanba6c5752018-04-02 11:46:38 -0700367 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700368 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chanba6c5752018-04-02 11:46:38 -0700369 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700370 continue;
371 }
372 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
373 }
374 }
375
376 if (!redoRouting(routeChanges, edgePairs, subnets)) {
377 log.debug("populateSubnet: populationStatus is ABORTED");
378 populationStatus = Status.ABORTED;
379 log.warn("Failed to repopulate the rules for subnet.");
380 return;
381 }
382
383 log.debug("populateSubnet: populationStatus is SUCCEEDED");
384 populationStatus = Status.SUCCEEDED;
385 log.info("Completed subnet population. Total # of rules pushed : {}",
386 rulePopulator.getCounter());
387 return;
388
389 } finally {
390 statusLock.unlock();
391 }
392 }
393
394 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700395 * Populates the routing rules or makes hash group changes according to the
396 * route-path changes due to link failure, switch failure or link up. This
397 * method should only be called for one of these three possible event-types.
Saurav Das604ab3a2018-03-18 21:28:15 -0700398 * Note that when a switch goes away, all of its links fail as well, but
399 * this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700400 *
Saurav Das604ab3a2018-03-18 21:28:15 -0700401 * @param linkDown the single failed link, or null for other conditions such
402 * as link-up or a removed switch
Saurav Dasc88d4662017-05-15 15:34:25 -0700403 * @param linkUp the single link up, or null for other conditions such as
Saurav Das604ab3a2018-03-18 21:28:15 -0700404 * link-down or a removed switch
405 * @param switchDown the removed switch, or null for other conditions such
406 * as link-down or link-up
407 * @param seenBefore true if this event is for a linkUp or linkDown for a
408 * seen link
409 */
410 // TODO This method should be refactored into three separated methods
Saurav Dasc88d4662017-05-15 15:34:25 -0700411 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
412 Link linkUp,
Saurav Das604ab3a2018-03-18 21:28:15 -0700413 DeviceId switchDown,
414 boolean seenBefore) {
415 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
416 .count() != 1) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700417 log.warn("Only one event can be handled for link status change .. aborting");
418 return;
419 }
Saurav Das604ab3a2018-03-18 21:28:15 -0700420
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700421 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900422 statusLock.lock();
423 try {
sangho20eff1d2015-04-13 15:15:58 -0700424
425 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700426 log.warn("Previous rule population is not finished. Cannot"
Saurav Dasc568c342018-01-25 09:49:01 -0800427 + " proceeed with routingRules for Topology change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700428 return;
sangho20eff1d2015-04-13 15:15:58 -0700429 }
430
Saurav Das7bcbe702017-06-13 15:35:54 -0700431 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700432 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700433 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700434 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530435 EcmpShortestPathGraph ecmpSpgUpdated =
436 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700437 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700438 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
439 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700440 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700441 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
442 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
443 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700444 }
sangho45b009c2015-05-07 13:30:57 -0700445 }
446
Saurav Dasc568c342018-01-25 09:49:01 -0800447 log.info("Starting to populate routing rules from Topology change");
sangho52abe3a2015-05-05 14:13:34 -0700448
sangho20eff1d2015-04-13 15:15:58 -0700449 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700450 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700451 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700452 populationStatus = Status.STARTED;
Saurav Dasc568c342018-01-25 09:49:01 -0800453 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
454 boolean hashGroupsChanged = false;
Saurav Das4e3224f2016-11-29 14:27:25 -0800455 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700456 if (linkDown == null) {
457 // either a linkUp or a switchDown - compute all route changes by
458 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dase0d4c872018-03-05 14:37:16 -0800459 routeChanges = computeRouteChange(switchDown);
Saurav Dasc88d4662017-05-15 15:34:25 -0700460
Saurav Das9df5b7c2017-08-14 16:44:43 -0700461 // deal with linkUp of a seen-before link
Saurav Das604ab3a2018-03-18 21:28:15 -0700462 if (linkUp != null && seenBefore) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700463 // link previously seen before
464 // do hash-bucket changes instead of a re-route
465 processHashGroupChange(routeChanges, false, null);
466 // clear out routesChanges so a re-route is not attempted
467 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800468 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700469 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700470 // for a linkUp of a never-seen-before link
471 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700472
Saurav Das9df5b7c2017-08-14 16:44:43 -0700473 //deal with switchDown
474 if (switchDown != null) {
475 processHashGroupChange(routeChanges, true, switchDown);
476 // clear out routesChanges so a re-route is not attempted
477 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800478 hashGroupsChanged = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700479 }
sangho20eff1d2015-04-13 15:15:58 -0700480 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700481 // link has gone down
482 // Compare existing ECMP SPG only with the link that went down
483 routeChanges = computeDamagedRoutes(linkDown);
484 if (routeChanges != null) {
485 processHashGroupChange(routeChanges, true, null);
486 // clear out routesChanges so a re-route is not attempted
487 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800488 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700489 }
sangho20eff1d2015-04-13 15:15:58 -0700490 }
491
Saurav Das4e3224f2016-11-29 14:27:25 -0800492 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700493 if (routeChanges == null) {
Saurav Dasc568c342018-01-25 09:49:01 -0800494 log.warn("Optimized routing failed... opting for full reroute");
Saurav Das7bcbe702017-06-13 15:35:54 -0700495 populationStatus = Status.ABORTED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700496 populateAllRoutingRules();
497 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700498 }
499
sangho20eff1d2015-04-13 15:15:58 -0700500 if (routeChanges.isEmpty()) {
Saurav Dasc568c342018-01-25 09:49:01 -0800501 if (hashGroupsChanged) {
502 log.info("Hash-groups changed for link status change");
503 } else {
504 log.info("No re-route or re-hash attempted for the link"
505 + " status change");
506 updatedEcmpSpgMap.keySet().forEach(devId -> {
507 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
508 log.debug("Updating ECMPspg for remaining dev:{}", devId);
509 });
510 }
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700511 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700512 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700513 return;
sangho20eff1d2015-04-13 15:15:58 -0700514 }
515
Saurav Dasc88d4662017-05-15 15:34:25 -0700516 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700517 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700518 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700519 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700520 log.info("Completed repopulation of rules for link-status change."
521 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700522 return;
sangho20eff1d2015-04-13 15:15:58 -0700523 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700524 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700525 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700526 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700527 return;
sangho20eff1d2015-04-13 15:15:58 -0700528 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900529 } finally {
530 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700531 }
532 }
533
Saurav Dasc88d4662017-05-15 15:34:25 -0700534 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700535 * Processes a set a route-path changes by reprogramming routing rules and
536 * creating new hash-groups or editing them if necessary. This method also
537 * determines the next-hops for the route-path from the src-switch (target)
538 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700539 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700540 * @param routeChanges a set of route-path changes, where each route-path is
541 * a list with its first element the src-switch (target)
542 * of the path, and the second element the dst-switch of
543 * the path.
544 * @param edgePairs a set of edge-switches that are paired by configuration
545 * @param subnets a set of prefixes that need to be populated in the routing
546 * table of the target switch in the route-path. Can be null,
547 * in which case all the prefixes belonging to the dst-switch
548 * will be populated in the target switch
549 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700550 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700551 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
552 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
553 // first make every entry two-elements
554 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
555 for (ArrayList<DeviceId> route : routeChanges) {
556 if (route.size() == 1) {
557 DeviceId dstSw = route.get(0);
558 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
559 if (ec == null) {
560 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
561 return false;
562 }
563 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
564 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
565 changedRoutes.add(Lists.newArrayList(target, dstSw));
566 });
567 });
568 } else {
569 DeviceId targetSw = route.get(0);
570 DeviceId dstSw = route.get(1);
571 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
572 }
573 }
574
575 // now process changedRoutes according to edgePairs
576 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
577 return false; //abort routing and fail fast
578 }
579
580 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Dasc568c342018-01-25 09:49:01 -0800581 Set<DeviceId> updatedDevices = Sets.newHashSet();
582 if (!redoRoutingIndividualDests(subnets, changedRoutes,
583 updatedDevices)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700584 return false; //abort routing and fail fast
585 }
586
Saurav Das7bcbe702017-06-13 15:35:54 -0700587 // update ecmpSPG for all edge-pairs
588 for (EdgePair ep : edgePairs) {
589 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
590 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
591 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
592 }
Saurav Dasc568c342018-01-25 09:49:01 -0800593
594 // here is where we update all devices not touched by this instance
595 updatedEcmpSpgMap.keySet().stream()
596 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
597 .filter(devId -> !updatedDevices.contains(devId))
598 .forEach(devId -> {
599 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
600 log.debug("Updating ECMPspg for remaining dev:{}", devId);
601 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700602 return true;
603 }
604
605 /**
606 * Programs targetSw in the changedRoutes for given prefixes reachable by
607 * an edgePair. If no prefixes are given, the method will use configured
608 * subnets/prefixes. If some configured subnets belong only to a specific
609 * destination in the edgePair, then the target switch will be programmed
610 * only to that destination.
611 *
612 * @param edgePairs set of edge-pairs for which target will be programmed
613 * @param subnets a set of prefixes that need to be populated in the routing
614 * table of the target switch in the changedRoutes. Can be null,
615 * in which case all the configured prefixes belonging to the
616 * paired switches will be populated in the target switch
617 * @param changedRoutes a set of route-path changes, where each route-path is
618 * a list with its first element the src-switch (target)
619 * of the path, and the second element the dst-switch of
620 * the path.
621 * @return true if successful
622 */
623 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
624 Set<IpPrefix> subnets,
625 Set<ArrayList<DeviceId>> changedRoutes) {
626 for (EdgePair ep : edgePairs) {
627 // temp store for a target's changedRoutes to this edge-pair
628 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
629 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
630 while (i.hasNext()) {
631 ArrayList<DeviceId> route = i.next();
632 DeviceId dstSw = route.get(1);
633 if (ep.includes(dstSw)) {
634 // routeChange for edge pair found
635 // sort by target iff target is edge and remove from changedRoutes
636 DeviceId targetSw = route.get(0);
637 try {
638 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
639 continue;
640 }
641 } catch (DeviceConfigNotFoundException e) {
642 log.warn(e.getMessage() + "aborting redoRouting");
643 return false;
644 }
645 // route is from another edge to this edge-pair
646 if (targetRoutes.containsKey(targetSw)) {
647 targetRoutes.get(targetSw).add(route);
648 } else {
649 Set<ArrayList<DeviceId>> temp = new HashSet<>();
650 temp.add(route);
651 targetRoutes.put(targetSw, temp);
652 }
653 i.remove();
654 }
655 }
656 // so now for this edgepair we have a per target set of routechanges
657 // process target->edgePair route
658 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
659 targetRoutes.entrySet()) {
660 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
661 entry.getKey(), ep);
662 DeviceId targetSw = entry.getKey();
663 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
664 entry.getValue().forEach(route -> {
665 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
666 log.debug("route: target {} -> dst {} found with next-hops {}",
667 route.get(0), route.get(1), nhops);
668 perDstNextHops.put(route.get(1), nhops);
669 });
670 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
671 : subnets;
672 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
673 : subnets;
674 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
675 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Dasc568c342018-01-25 09:49:01 -0800676 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
677 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700678 // handle routing to subnets common to edge-pair
Saurav Dasc568c342018-01-25 09:49:01 -0800679 // only if the targetSw is not part of the edge-pair and there
680 // exists a next hop to at least one of the devices in the edge-pair
681 if (!ep.includes(targetSw)
682 && ((nhDev1 != null && !nhDev1.isEmpty())
683 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700684 if (!populateEcmpRoutingRulePartial(
685 targetSw,
686 ep.dev1, ep.dev2,
687 perDstNextHops,
688 Sets.intersection(ipDev1, ipDev2))) {
689 return false; // abort everything and fail fast
690 }
691 }
Saurav Dasc568c342018-01-25 09:49:01 -0800692 // handle routing to subnets that only belong to dev1 only if
693 // a next-hop exists from the target to dev1
Saurav Das7bcbe702017-06-13 15:35:54 -0700694 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Dasc568c342018-01-25 09:49:01 -0800695 if (!onlyDev1Subnets.isEmpty()
696 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700697 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800698 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das7bcbe702017-06-13 15:35:54 -0700699 if (!populateEcmpRoutingRulePartial(
700 targetSw,
701 ep.dev1, null,
702 onlyDev1NextHops,
703 onlyDev1Subnets)) {
704 return false; // abort everything and fail fast
705 }
706 }
Saurav Dasc568c342018-01-25 09:49:01 -0800707 // handle routing to subnets that only belong to dev2 only if
708 // a next-hop exists from the target to dev2
Saurav Das7bcbe702017-06-13 15:35:54 -0700709 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Dasc568c342018-01-25 09:49:01 -0800710 if (!onlyDev2Subnets.isEmpty()
711 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700712 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800713 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700714 if (!populateEcmpRoutingRulePartial(
715 targetSw,
716 ep.dev2, null,
717 onlyDev2NextHops,
718 onlyDev2Subnets)) {
719 return false; // abort everything and fail fast
720 }
721 }
722 }
723 // if it gets here it has succeeded for all targets to this edge-pair
724 }
725 return true;
726 }
727
728 /**
729 * Programs targetSw in the changedRoutes for given prefixes reachable by
730 * a destination switch that is not part of an edge-pair.
731 * If no prefixes are given, the method will use configured subnets/prefixes.
732 *
733 * @param subnets a set of prefixes that need to be populated in the routing
734 * table of the target switch in the changedRoutes. Can be null,
735 * in which case all the configured prefixes belonging to the
736 * paired switches will be populated in the target switch
737 * @param changedRoutes a set of route-path changes, where each route-path is
738 * a list with its first element the src-switch (target)
739 * of the path, and the second element the dst-switch of
740 * the path.
741 * @return true if successful
742 */
743 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Dasc568c342018-01-25 09:49:01 -0800744 Set<ArrayList<DeviceId>> changedRoutes,
745 Set<DeviceId> updatedDevices) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700746 // aggregate route-path changes for each dst device
747 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
748 new HashMap<>();
749 for (ArrayList<DeviceId> route: changedRoutes) {
750 DeviceId dstSw = route.get(1);
751 ArrayList<ArrayList<DeviceId>> deviceRoutes =
752 routesBydevice.get(dstSw);
753 if (deviceRoutes == null) {
754 deviceRoutes = new ArrayList<>();
755 routesBydevice.put(dstSw, deviceRoutes);
756 }
757 deviceRoutes.add(route);
758 }
759 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
760 ArrayList<ArrayList<DeviceId>> deviceRoutes =
761 routesBydevice.get(impactedDstDevice);
762 for (ArrayList<DeviceId> route: deviceRoutes) {
763 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
764 route.get(0), route.get(1));
765 DeviceId targetSw = route.get(0);
766 DeviceId dstSw = route.get(1); // same as impactedDstDevice
767 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Dasbd071d82018-01-09 17:38:44 -0800768 if (nextHops.isEmpty()) {
769 log.warn("Could not find next hop from target:{} --> dst {} "
770 + "skipping this route", targetSw, dstSw);
771 continue;
772 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700773 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
774 nhops.put(dstSw, nextHops);
775 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
776 (subnets == null) ? Sets.newHashSet() : subnets)) {
777 return false; // abort routing and fail fast
778 }
779 log.debug("Populating flow rules from target: {} to dst: {}"
780 + " is successful", targetSw, dstSw);
781 }
782 //Only if all the flows for all impacted routes to a
783 //specific target are pushed successfully, update the
784 //ECMP graph for that target. Or else the next event
785 //would not see any changes in the ECMP graphs.
786 //In another case, the target switch has gone away, so
787 //routes can't be installed. In that case, the current map
788 //is updated here, without any flows being pushed.
789 currentEcmpSpgMap.put(impactedDstDevice,
790 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Dasc568c342018-01-25 09:49:01 -0800791 updatedDevices.add(impactedDstDevice);
Saurav Das7bcbe702017-06-13 15:35:54 -0700792 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
793 }
794 return true;
795 }
796
797 /**
798 * Populate ECMP rules for subnets from target to destination via nexthops.
799 *
800 * @param targetSw Device ID of target switch in which rules will be programmed
801 * @param destSw1 Device ID of final destination switch to which the rules will forward
802 * @param destSw2 Device ID of paired destination switch to which the rules will forward
803 * A null deviceId indicates packets should only be sent to destSw1
Saurav Dasa4020382018-02-14 14:14:54 -0800804 * @param nextHops Map of a set of next hops per destSw
Saurav Das7bcbe702017-06-13 15:35:54 -0700805 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
806 * @return true if it succeeds in populating rules
807 */ // refactor
808 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
809 DeviceId destSw1,
810 DeviceId destSw2,
811 Map<DeviceId, Set<DeviceId>> nextHops,
812 Set<IpPrefix> subnets) {
813 boolean result;
814 // If both target switch and dest switch are edge routers, then set IP
815 // rule for both subnet and router IP.
816 boolean targetIsEdge;
817 boolean dest1IsEdge;
818 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
819 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
820
821 try {
822 targetIsEdge = config.isEdgeDevice(targetSw);
823 dest1IsEdge = config.isEdgeDevice(destSw1);
824 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
825 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
826 if (destSw2 != null) {
827 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
828 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
829 }
830 } catch (DeviceConfigNotFoundException e) {
831 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700832 return false;
833 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700834
835 if (targetIsEdge && dest1IsEdge) {
836 subnets = (subnets != null && !subnets.isEmpty())
837 ? Sets.newHashSet(subnets)
838 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Dasa4020382018-02-14 14:14:54 -0800839 // XXX - Rethink this - ignoring routerIPs in all other switches
840 // even edge to edge switches
Saurav Das7bcbe702017-06-13 15:35:54 -0700841 /*subnets.add(dest1RouterIpv4.toIpPrefix());
842 if (dest1RouterIpv6 != null) {
843 subnets.add(dest1RouterIpv6.toIpPrefix());
844 }
845 if (destSw2 != null && dest2RouterIpv4 != null) {
846 subnets.add(dest2RouterIpv4.toIpPrefix());
847 if (dest2RouterIpv6 != null) {
848 subnets.add(dest2RouterIpv6.toIpPrefix());
849 }
850 }*/
851 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
852 + "for subnets {}", targetSw, destSw1,
853 (destSw2 != null) ? ("& " + destSw2) : "",
854 subnets);
855 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
856 destSw1, destSw2,
857 nextHops);
858 if (!result) {
859 return false;
860 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700861 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700862
863 if (!targetIsEdge && dest1IsEdge) {
864 // MPLS rules in all non-edge target devices. These rules are for
865 // individual destinations, even if the dsts are part of edge-pairs.
866 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
867 + "all MPLS rules", targetSw, destSw1);
868 result = rulePopulator.populateMplsRule(targetSw, destSw1,
869 nextHops.get(destSw1),
870 dest1RouterIpv4);
871 if (!result) {
872 return false;
873 }
874 if (dest1RouterIpv6 != null) {
Saurav Dasa4020382018-02-14 14:14:54 -0800875 int v4sid = 0, v6sid = 0;
876 try {
877 v4sid = config.getIPv4SegmentId(destSw1);
878 v6sid = config.getIPv6SegmentId(destSw1);
879 } catch (DeviceConfigNotFoundException e) {
880 log.warn(e.getMessage());
881 }
882 if (v4sid != v6sid) {
883 result = rulePopulator.populateMplsRule(targetSw, destSw1,
884 nextHops.get(destSw1),
885 dest1RouterIpv6);
886 if (!result) {
887 return false;
888 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700889 }
890 }
891 }
892
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -0700893 if (!targetIsEdge && !dest1IsEdge) {
894 // MPLS rules for inter-connected spines
895 // can be merged with above if, left it here for clarity
896 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
897 + "all MPLS rules", targetSw, destSw1);
898
899 result = rulePopulator.populateMplsRule(targetSw, destSw1,
900 nextHops.get(destSw1),
901 dest1RouterIpv4);
902 if (!result) {
903 return false;
904 }
905
906 if (dest1RouterIpv6 != null) {
907 int v4sid = 0, v6sid = 0;
908 try {
909 v4sid = config.getIPv4SegmentId(destSw1);
910 v6sid = config.getIPv6SegmentId(destSw1);
911 } catch (DeviceConfigNotFoundException e) {
912 log.warn(e.getMessage());
913 }
914 if (v4sid != v6sid) {
915 result = rulePopulator.populateMplsRule(targetSw, destSw1,
916 nextHops.get(destSw1),
917 dest1RouterIpv6);
918 if (!result) {
919 return false;
920 }
921 }
922 }
923 }
924
925
Saurav Das7bcbe702017-06-13 15:35:54 -0700926 // To save on ECMP groups
927 // avoid MPLS rules in non-edge-devices to non-edge-devices
928 // avoid MPLS transit rules in edge-devices
929 // avoid loopback IP rules in edge-devices to non-edge-devices
930 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700931 }
932
933 /**
934 * Processes a set a route-path changes by editing hash groups.
935 *
936 * @param routeChanges a set of route-path changes, where each route-path is
937 * a list with its first element the src-switch of the path
938 * and the second element the dst-switch of the path.
939 * @param linkOrSwitchFailed true if the route changes are for a failed
940 * switch or linkDown event
941 * @param failedSwitch the switchId if the route changes are for a failed switch,
942 * otherwise null
943 */
944 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
945 boolean linkOrSwitchFailed,
946 DeviceId failedSwitch) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700947 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
948 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700949 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700950 if (route.size() == 1) {
951 // route-path changes are from everyone else to this switch
952 DeviceId dstSw = route.get(0);
953 srManager.deviceService.getAvailableDevices().forEach(sw -> {
954 if (!sw.id().equals(dstSw)) {
955 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
956 }
957 });
958 } else {
959 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700960 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700961 }
Saurav Dasc568c342018-01-25 09:49:01 -0800962 boolean someFailed = false;
963 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Das9df5b7c2017-08-14 16:44:43 -0700964 for (ArrayList<DeviceId> route : changedRoutes) {
965 DeviceId targetSw = route.get(0);
966 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700967 if (linkOrSwitchFailed) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700968 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700969 // it's possible that we cannot fix hash groups for a route
970 // if the target switch has failed. Nevertheless the ecmp graph
971 // for the impacted switch must still be updated.
Saurav Das9df5b7c2017-08-14 16:44:43 -0700972 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700973 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
974 currentEcmpSpgMap.remove(targetSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700975 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -0700976 + "target:{}", dstSw, targetSw);
Saurav Dasc568c342018-01-25 09:49:01 -0800977 updatedDevices.add(targetSw);
978 updatedDevices.add(dstSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700979 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -0700980 }
981 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -0700982 if (success) {
983 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das9df5b7c2017-08-14 16:44:43 -0700984 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Dasc568c342018-01-25 09:49:01 -0800985 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
986 + " or switchdown", dstSw, targetSw);
987 updatedDevices.add(targetSw);
988 updatedDevices.add(dstSw);
989 } else {
990 someFailed = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700991 }
992 } else {
993 //linkup of seen before link
994 boolean success = fixHashGroupsForRoute(route, false);
995 if (success) {
996 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
997 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
998 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -0700999 targetSw, dstSw);
Saurav Dasc568c342018-01-25 09:49:01 -08001000 updatedDevices.add(targetSw);
1001 updatedDevices.add(dstSw);
1002 } else {
1003 someFailed = true;
Saurav Dasc88d4662017-05-15 15:34:25 -07001004 }
1005 }
1006 }
Saurav Dasc568c342018-01-25 09:49:01 -08001007 if (!someFailed) {
1008 // here is where we update all devices not touched by this instance
1009 updatedEcmpSpgMap.keySet().stream()
1010 .filter(devId -> !updatedDevices.contains(devId))
1011 .forEach(devId -> {
1012 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1013 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1014 });
1015 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001016 }
1017
1018 /**
1019 * Edits hash groups in the src-switch (targetSw) of a route-path by
1020 * calling the groupHandler to either add or remove buckets in an existing
1021 * hash group.
1022 *
1023 * @param route a single list representing a route-path where the first element
1024 * is the src-switch (targetSw) of the route-path and the
1025 * second element is the dst-switch
1026 * @param revoke true if buckets in the hash-groups need to be removed;
1027 * false if buckets in the hash-groups need to be added
1028 * @return true if the hash group editing is successful
1029 */
1030 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1031 boolean revoke) {
1032 DeviceId targetSw = route.get(0);
1033 if (route.size() < 2) {
1034 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1035 return false;
1036 }
1037 DeviceId destSw = route.get(1);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001038 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001039 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001040 // figure out the new next hops at the targetSw towards the destSw
Saurav Das9df5b7c2017-08-14 16:44:43 -07001041 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001042 // call group handler to change hash group at targetSw
1043 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1044 if (grpHandler == null) {
1045 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1046 + " {} hash group buckets for route:{} ", targetSw,
1047 (revoke) ? "revoke" : "repopulate", route);
1048 return false;
1049 }
1050 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
1051 (revoke) ? "revoke" : "repopulating",
1052 targetSw, destSw, nextHops);
1053 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1054 destSw, true)
1055 : grpHandler.fixHashGroups(targetSw, nextHops,
1056 destSw, false);
1057 }
1058
1059 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001060 * Start the flow rule population process if it was never started. The
1061 * process finishes successfully when all flow rules are set and stops with
1062 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -07001063 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001064 public void startPopulationProcess() {
1065 statusLock.lock();
1066 try {
1067 if (populationStatus == Status.IDLE
1068 || populationStatus == Status.SUCCEEDED
1069 || populationStatus == Status.ABORTED) {
1070 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -07001071 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -07001072 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1073 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001074 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001075 } finally {
1076 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001077 }
sangho20eff1d2015-04-13 15:15:58 -07001078 }
1079
Saurav Dasb5c236e2016-06-07 10:08:06 -07001080 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001081 * Revoke rules of given subnet in all edge switches.
1082 *
1083 * @param subnets subnet being removed
1084 * @return true if succeed
1085 */
1086 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1087 statusLock.lock();
1088 try {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001089 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1090 .map(Device::id)
1091 .filter(this::shouldProgram)
1092 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das7bcbe702017-06-13 15:35:54 -07001093 } finally {
1094 statusLock.unlock();
1095 }
1096 }
1097
1098 /**
Charles Chan2fde6d42017-08-23 14:46:43 -07001099 * Populates IP rules for a route that has direct connection to the switch
1100 * if the current instance is the master of the switch.
1101 *
1102 * @param deviceId device ID of the device that next hop attaches to
1103 * @param prefix IP prefix of the route
1104 * @param hostMac MAC address of the next hop
1105 * @param hostVlanId Vlan ID of the nexthop
1106 * @param outPort port where the next hop attaches to
1107 */
1108 void populateRoute(DeviceId deviceId, IpPrefix prefix,
1109 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001110 if (shouldProgram(deviceId)) {
Charles Chan2fde6d42017-08-23 14:46:43 -07001111 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1112 }
1113 }
1114
1115 /**
1116 * Removes IP rules for a route when the next hop is gone.
1117 * if the current instance is the master of the switch.
1118 *
1119 * @param deviceId device ID of the device that next hop attaches to
1120 * @param prefix IP prefix of the route
1121 * @param hostMac MAC address of the next hop
1122 * @param hostVlanId Vlan ID of the nexthop
1123 * @param outPort port that next hop attaches to
1124 */
1125 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1126 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001127 if (shouldProgram(deviceId)) {
Charles Chan2fde6d42017-08-23 14:46:43 -07001128 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1129 }
1130 }
1131
Charles Chan2ff1bac2018-03-29 16:03:41 -07001132 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1133 if (shouldProgram(deviceId)) {
1134 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1135 }
1136 }
1137
1138 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1139 if (shouldProgram(deviceId)) {
1140 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1141 }
1142 }
1143
1144 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1145 VlanId vlanId, boolean popVlan, boolean install) {
1146 if (shouldProgram(deviceId)) {
1147 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1148 }
1149 }
1150
1151 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1152 VlanId vlanId, boolean popVlan, boolean install) {
1153 if (shouldProgram(deviceId)) {
1154 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1155 vlanId, popVlan, install);
1156 }
1157 }
1158
Charles Chan2fde6d42017-08-23 14:46:43 -07001159 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001160 * Remove ECMP graph entry for the given device. Typically called when
1161 * device is no longer available.
1162 *
1163 * @param deviceId the device for which graphs need to be purged
1164 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001165 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Dasc568c342018-01-25 09:49:01 -08001166 statusLock.lock();
1167 try {
1168
1169 if (populationStatus == Status.STARTED) {
1170 log.warn("Previous rule population is not finished. Cannot"
1171 + " proceeed with purgeEcmpGraph for {}", deviceId);
1172 return;
1173 }
1174 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1175 currentEcmpSpgMap.remove(deviceId);
1176 if (updatedEcmpSpgMap != null) {
1177 updatedEcmpSpgMap.remove(deviceId);
1178 }
1179 } finally {
1180 statusLock.unlock();
Saurav Das7bcbe702017-06-13 15:35:54 -07001181 }
1182 }
1183
1184 //////////////////////////////////////
1185 // Routing helper methods and classes
1186 //////////////////////////////////////
1187
1188 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001189 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001190 * previous ecmp shortest-path graph exists for a switch in order to compute
1191 * affected routes. If such a graph does not exist, the method returns null.
1192 *
1193 * @param linkFail the failed link
1194 * @return the set of affected routes which may be empty if no routes were
1195 * affected, or null if no previous ecmp spg was found for comparison
1196 */
sangho20eff1d2015-04-13 15:15:58 -07001197 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001198 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1199
1200 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001201 log.debug("Computing the impacted routes for device {} due to link fail",
1202 sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001203 if (!shouldProgram(sw.id())) {
sangho20eff1d2015-04-13 15:15:58 -07001204 continue;
1205 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001206 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das9df5b7c2017-08-14 16:44:43 -07001207 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1208 if (ecmpSpg == null) {
1209 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1210 + " rerouting and opting for full-reroute", rootSw);
1211 return null;
1212 }
1213 if (log.isDebugEnabled()) {
1214 log.debug("Root switch: {}", rootSw);
1215 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1216 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1217 }
1218 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1219 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1220 // figure out if the broken link affected any route-paths in this graph
1221 for (Integer itrIdx : switchVia.keySet()) {
1222 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1223 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1224 switchVia.get(itrIdx);
1225 for (DeviceId targetSw : swViaMap.keySet()) {
1226 log.trace("TargetSwitch {} --> RootSwitch {}",
1227 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001228 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1229 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001230 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001231 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001232 Set<ArrayList<DeviceId>> subLinks =
1233 computeLinks(targetSw, rootSw, swViaMap);
1234 for (ArrayList<DeviceId> alink: subLinks) {
1235 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1236 alink.get(1).equals(linkFail.dst().deviceId()))
1237 ||
1238 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1239 alink.get(1).equals(linkFail.src().deviceId()))) {
1240 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1241 ArrayList<DeviceId> aRoute = new ArrayList<>();
1242 aRoute.add(targetSw); // switch with rules to populate
1243 aRoute.add(rootSw); // towards this destination
1244 routes.add(aRoute);
1245 break;
1246 }
sangho20eff1d2015-04-13 15:15:58 -07001247 }
1248 }
1249 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001250
sangho20eff1d2015-04-13 15:15:58 -07001251 }
sangho45b009c2015-05-07 13:30:57 -07001252
sangho20eff1d2015-04-13 15:15:58 -07001253 }
sangho20eff1d2015-04-13 15:15:58 -07001254 return routes;
1255 }
1256
Saurav Das4e3224f2016-11-29 14:27:25 -08001257 /**
1258 * Computes set of affected routes due to new links or failed switches.
1259 *
Saurav Das604ab3a2018-03-18 21:28:15 -07001260 * @param failedSwitch deviceId of failed switch if any
Saurav Das4e3224f2016-11-29 14:27:25 -08001261 * @return the set of affected routes which may be empty if no routes were
1262 * affected
1263 */
Saurav Dase0d4c872018-03-05 14:37:16 -08001264 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001265 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001266 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001267
1268 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001269 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001270 if (!shouldProgram(sw.id())) {
sangho20eff1d2015-04-13 15:15:58 -07001271 continue;
1272 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001273 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001274 if (log.isTraceEnabled()) {
1275 log.trace("Device links for dev: {}", rootSw);
1276 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1277 log.trace("{} -> {} ", link.src().deviceId(),
1278 link.dst().deviceId());
1279 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001280 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001281 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1282 if (currEcmpSpg == null) {
1283 log.debug("No existing ECMP graph for device {}.. adding self as "
1284 + "changed route", rootSw);
1285 changedRtBldr.add(Lists.newArrayList(rootSw));
1286 continue;
1287 }
1288 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Das5a356042018-04-06 20:16:01 -07001289 if (newEcmpSpg == null) {
1290 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1291 continue;
1292 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001293 if (log.isDebugEnabled()) {
1294 log.debug("Root switch: {}", rootSw);
1295 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1296 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1297 }
1298 // first use the updated/new map to compare to current/existing map
1299 // as new links may have come up
1300 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1301 // then use the current/existing map to compare to updated/new map
1302 // as switch may have been removed
1303 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001304 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001305 }
sangho20eff1d2015-04-13 15:15:58 -07001306
Saurav Dase0d4c872018-03-05 14:37:16 -08001307 // handle clearing state for a failed switch in case the switch does
1308 // not have a pair, or the pair is not available
1309 if (failedSwitch != null) {
Charles Chanba6c5752018-04-02 11:46:38 -07001310 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1311 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dase0d4c872018-03-05 14:37:16 -08001312 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1313 srManager.deviceService.getDevices().forEach(dev -> {
1314 if (!dev.id().equals(failedSwitch) &&
1315 srManager.mastershipService.isLocalMaster(dev.id())) {
1316 log.debug(" : {}", dev.id());
1317 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1318 }
1319 });
1320 }
1321 }
1322
Saurav Das7bcbe702017-06-13 15:35:54 -07001323 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001324 for (ArrayList<DeviceId> route: changedRoutes) {
1325 log.debug("Route changes Target -> Root");
1326 if (route.size() == 1) {
1327 log.debug(" : all -> {}", route.get(0));
1328 } else {
1329 log.debug(" : {} -> {}", route.get(0), route.get(1));
1330 }
1331 }
1332 return changedRoutes;
1333 }
1334
1335 /**
1336 * For the root switch, searches all the target nodes reachable in the base
1337 * graph, and compares paths to the ones in the comp graph.
1338 *
1339 * @param base the graph that is indexed for all reachable target nodes
1340 * from the root node
1341 * @param comp the graph that the base graph is compared to
1342 * @param rootSw both ecmp graphs are calculated for the root node
1343 * @return all the routes that have changed in the base graph
1344 */
1345 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1346 EcmpShortestPathGraph comp,
1347 DeviceId rootSw) {
1348 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1349 ImmutableSet.builder();
1350 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1351 base.getAllLearnedSwitchesAndVia();
1352 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1353 comp.getAllLearnedSwitchesAndVia();
1354 for (Integer itrIdx : baseMap.keySet()) {
1355 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1356 baseMap.get(itrIdx);
1357 for (DeviceId targetSw : baseViaMap.keySet()) {
1358 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1359 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1360 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001361 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001362 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001363 route.add(targetSw); // switch with rules to populate
1364 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001365 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001366 }
1367 }
sangho45b009c2015-05-07 13:30:57 -07001368 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001369 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001370 }
1371
Saurav Das7bcbe702017-06-13 15:35:54 -07001372 /**
1373 * Returns the ECMP paths traversed to reach the target switch.
1374 *
1375 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1376 * @param targetSw the switch to reach from the root switch
1377 * @return the nodes traversed on ECMP paths to the target switch
1378 */
sangho20eff1d2015-04-13 15:15:58 -07001379 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001380 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001381 for (Integer itrIdx : switchVia.keySet()) {
1382 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1383 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001384 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001385 continue;
1386 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001387 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001388 }
1389 }
1390
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001391 return null;
sangho20eff1d2015-04-13 15:15:58 -07001392 }
1393
Saurav Das7bcbe702017-06-13 15:35:54 -07001394 /**
1395 * Utility method to break down a path from src to dst device into a collection
1396 * of links.
1397 *
1398 * @param src src device of the path
1399 * @param dst dst device of the path
1400 * @param viaMap path taken from src to dst device
1401 * @return collection of links in the path
1402 */
sangho20eff1d2015-04-13 15:15:58 -07001403 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1404 DeviceId dst,
1405 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1406 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1407 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1408 DeviceId linkSrc = src;
1409 DeviceId linkDst = dst;
1410 for (DeviceId viaDevice: via) {
1411 ArrayList<DeviceId> link = new ArrayList<>();
1412 linkDst = viaDevice;
1413 link.add(linkSrc);
1414 link.add(linkDst);
1415 subLinks.add(link);
1416 linkSrc = viaDevice;
1417 }
1418 ArrayList<DeviceId> link = new ArrayList<>();
1419 link.add(linkSrc);
1420 link.add(dst);
1421 subLinks.add(link);
1422 }
1423
1424 return subLinks;
1425 }
1426
Charles Chan93e71ba2016-04-29 14:38:22 -07001427 /**
Charles Chan2ff1bac2018-03-29 16:03:41 -07001428 * Determines whether this controller instance should program the
Saurav Das7bcbe702017-06-13 15:35:54 -07001429 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chan2ff1bac2018-03-29 16:03:41 -07001430 * <p>
1431 * Once an instance is elected, it will be the only instance responsible for programming
1432 * both devices in the pair until it goes down.
Charles Chan93e71ba2016-04-29 14:38:22 -07001433 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001434 * @param deviceId device identifier to consider for routing
Charles Chan2ff1bac2018-03-29 16:03:41 -07001435 * @return true if current instance should handle the routing for given device
Charles Chan93e71ba2016-04-29 14:38:22 -07001436 */
Charles Chan2ff1bac2018-03-29 16:03:41 -07001437 boolean shouldProgram(DeviceId deviceId) {
Charles Chan50bb6ef2018-04-18 18:41:05 -07001438 Boolean cached = shouldProgramCache.get(deviceId);
1439 if (cached != null) {
1440 return cached;
1441 }
1442
Charles Chan2ff1bac2018-03-29 16:03:41 -07001443 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001444
Charles Chan2ff1bac2018-03-29 16:03:41 -07001445 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1446 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1447 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
1448 log.debug("Evaluate shouldProgram {}/pair={}. current={}, master={}, pairMaster={}",
1449 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1450
1451 // No pair device configured. Only handle when current instance is the master of the device
1452 if (!pairDeviceId.isPresent()) {
1453 log.debug("No pair device. current={}, master={}", currentNodeId, masterNodeId);
1454 return currentNodeId.equals(masterNodeId);
sanghob35a6192015-04-01 13:05:26 -07001455 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001456
1457 // Should not handle if current instance is not the master of either switch
1458 if (!currentNodeId.equals(masterNodeId) &&
1459 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
1460 log.debug("Current node {} is neither the master of target device {} nor pair device {}",
1461 currentNodeId, deviceId, pairDeviceId);
1462 return false;
1463 }
1464
1465 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1466
1467 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1468 if (v == null) {
1469 // There is no value in the map. Elect a node
1470 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1471 } else {
1472 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1473 // Use the node in the map if it is still alive and is a master of any of the two switches
1474 return v;
1475 } else {
1476 // Previously elected node is no longer the master of either switch. Re-elect a node.
1477 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1478 }
1479 }
1480 }));
1481
1482 if (king != null) {
1483 log.debug("{} should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001484 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chan2ff1bac2018-03-29 16:03:41 -07001485 return king.equals(currentNodeId);
1486 } else {
1487 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001488 shouldProgramCache.remove(deviceId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001489 return false;
1490 }
1491 }
1492
1493 /**
1494 * Elects a node who should take responsibility of programming devices.
1495 * @param nodeIds list of candidate node ID
1496 *
1497 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1498 */
1499 private NodeId elect(List<NodeId> nodeIds) {
1500 // Remove all null elements. This could happen when some device has no master
1501 nodeIds.removeAll(Collections.singleton(null));
1502 nodeIds.sort(null);
1503 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1504 }
1505
Charles Chan50bb6ef2018-04-18 18:41:05 -07001506 void invalidateShouldProgramCache(DeviceId deviceId) {
1507 shouldProgramCache.remove(deviceId);
1508 }
1509
Charles Chan2ff1bac2018-03-29 16:03:41 -07001510 /**
1511 * Returns a set of device ID, containing given device and its pair device if exist.
1512 *
1513 * @param deviceId Device ID
1514 * @return a set of device ID, containing given device and its pair device if exist.
1515 */
1516 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1517 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1518 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1519 return ret;
sanghob35a6192015-04-01 13:05:26 -07001520 }
1521
Charles Chan93e71ba2016-04-29 14:38:22 -07001522 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001523 * Returns the set of deviceIds which are the next hops from the targetSw
1524 * to the dstSw according to the latest ECMP spg.
1525 *
1526 * @param targetSw the switch for which the next-hops are desired
1527 * @param dstSw the switch to which the next-hops lead to from the targetSw
1528 * @return set of next hop deviceIds, could be empty if no next hops are found
1529 */
1530 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1531 boolean targetIsEdge = false;
1532 try {
1533 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1534 } catch (DeviceConfigNotFoundException e) {
1535 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1536 + "continuing to getNextHops", targetSw);
1537 }
1538
1539 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1540 if (ecmpSpg == null) {
1541 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1542 return ImmutableSet.of();
1543 }
1544 HashMap<Integer,
1545 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1546 ecmpSpg.getAllLearnedSwitchesAndVia();
1547 for (Integer itrIdx : switchVia.keySet()) {
1548 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1549 switchVia.get(itrIdx);
1550 for (DeviceId target : swViaMap.keySet()) {
1551 if (!target.equals(targetSw)) {
1552 continue;
1553 }
1554 if (!targetIsEdge && itrIdx > 1) {
Saurav Dasa4020382018-02-14 14:14:54 -08001555 // optimization for spines to not use leaves to get
1556 // to a spine or other leaves
1557 boolean pathdevIsEdge = false;
1558 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1559 for (DeviceId pathdev : via) {
1560 try {
1561 pathdevIsEdge = srManager.deviceConfiguration
1562 .isEdgeDevice(pathdev);
1563 } catch (DeviceConfigNotFoundException e) {
1564 log.warn(e.getMessage());
1565 }
1566 if (pathdevIsEdge) {
1567 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1568 + " --> dstSw:{} which goes through an edge"
1569 + " device {} in path {}", itrIdx,
1570 targetSw, dstSw, pathdev, via);
1571 return ImmutableSet.of();
1572 }
1573 }
1574 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001575 }
1576 Set<DeviceId> nextHops = new HashSet<>();
1577 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1578 if (via.isEmpty()) {
1579 // the dstSw is the next-hop from the targetSw
1580 nextHops.add(dstSw);
1581 } else {
1582 // first elem is next-hop in each ECMP path
1583 nextHops.add(via.get(0));
1584 }
1585 }
1586 return nextHops;
1587 }
1588 }
1589 return ImmutableSet.of(); //no next-hops found
1590 }
1591
1592 /**
1593 * Represents two devices that are paired by configuration. An EdgePair for
1594 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1595 */
1596 protected final class EdgePair {
1597 DeviceId dev1;
1598 DeviceId dev2;
1599
1600 EdgePair(DeviceId dev1, DeviceId dev2) {
1601 this.dev1 = dev1;
1602 this.dev2 = dev2;
1603 }
1604
1605 boolean includes(DeviceId dev) {
1606 return dev1.equals(dev) || dev2.equals(dev);
1607 }
1608
1609 @Override
1610 public boolean equals(Object o) {
1611 if (this == o) {
1612 return true;
1613 }
1614 if (!(o instanceof EdgePair)) {
1615 return false;
1616 }
1617 EdgePair that = (EdgePair) o;
1618 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1619 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1620 }
1621
1622 @Override
1623 public int hashCode() {
1624 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1625 return Objects.hash(dev1, dev2);
1626 } else {
1627 return Objects.hash(dev2, dev1);
1628 }
1629 }
1630
1631 @Override
1632 public String toString() {
1633 return toStringHelper(this)
1634 .add("Dev1", dev1)
1635 .add("Dev2", dev2)
1636 .toString();
1637 }
1638 }
1639
1640 //////////////////////////////////////
1641 // Filtering rule creation
1642 //////////////////////////////////////
1643
1644 /**
Saurav Das018605f2017-02-18 14:05:44 -08001645 * Populates filtering rules for port, and punting rules
1646 * for gateway IPs, loopback IPs and arp/ndp traffic.
1647 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001648 *
1649 * @param deviceId Switch ID to set the rules
1650 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001651 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001652 // Although device is added, sometimes device store does not have the
1653 // ports for this device yet. It results in missing filtering rules in the
1654 // switch. We will attempt it a few times. If it still does not work,
1655 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001656 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001657 if (firstRun == null) {
1658 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001659 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001660 executorService.schedule(new RetryFilters(deviceId, firstRun),
1661 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001662 }
1663
1664 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001665 * Utility class used to temporarily store information about the ports on a
1666 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001667 */
1668 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001669 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001670
Saurav Das018605f2017-02-18 14:05:44 -08001671 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001672 int filteredPorts) {
1673 this.disabledPorts = disabledPorts;
1674 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001675 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001676 }
1677
1678 @Override
1679 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001680 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001681 }
1682
1683 @Override
1684 public boolean equals(Object obj) {
1685 if (this == obj) {
1686 return true;
1687 }
1688 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1689 return false;
1690 }
1691 PortFilterInfo other = (PortFilterInfo) obj;
1692 return ((disabledPorts == other.disabledPorts) &&
1693 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001694 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001695 }
1696
1697 @Override
1698 public String toString() {
1699 MoreObjects.ToStringHelper helper = toStringHelper(this)
1700 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001701 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001702 .add("filteredPorts", filteredPorts);
1703 return helper.toString();
1704 }
1705 }
1706
1707 /**
1708 * RetryFilters populates filtering objectives for a device and keeps retrying
1709 * till the number of ports filtered are constant for a predefined number
1710 * of attempts.
1711 */
1712 protected final class RetryFilters implements Runnable {
1713 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1714 DeviceId devId;
1715 int counter;
1716 PortFilterInfo prevRun;
1717
1718 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001719 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001720 prevRun = previousRun;
1721 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001722 }
1723
1724 @Override
1725 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001726 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001727 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001728 boolean sameResult = prevRun.equals(thisRun);
1729 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1730 thisRun, sameResult);
Ray Milkeyc6c9b172018-02-26 09:36:31 -08001731 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001732 // exponentially increasing intervals for retries
1733 executorService.schedule(this,
1734 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1735 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001736 if (!sameResult) {
1737 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1738 }
Saurav Das59232cf2016-04-27 18:35:50 -07001739 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001740 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001741 }
Saurav Das59232cf2016-04-27 18:35:50 -07001742 }
1743
sanghob35a6192015-04-01 13:05:26 -07001744}