blob: 8b4b31c1352583b6c07b339861420080e0d66c17 [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 Dasc88d4662017-05-15 15:34:25 -070018import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableMap.Builder;
Charles Chan93e71ba2016-04-29 14:38:22 -070020import com.google.common.collect.ImmutableSet;
Saurav Das4e3224f2016-11-29 14:27:25 -080021import com.google.common.collect.Lists;
sangho20eff1d2015-04-13 15:15:58 -070022import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
Saurav Dasceccf242017-08-03 18:30:35 -070024
Jonghwan Hyun800d9d02018-04-09 09:40:50 -070025import org.onlab.packet.EthType;
Charles Chan411beb22019-04-17 14:20:26 -070026import com.google.common.collect.Streams;
sangho666cd6d2015-04-14 16:27:13 -070027import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080028import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070029import org.onlab.packet.IpPrefix;
Charles Chan2fde6d42017-08-23 14:46:43 -070030import org.onlab.packet.MacAddress;
31import org.onlab.packet.VlanId;
piere08595d2019-04-24 16:12:47 +020032import org.onlab.util.PredictableExecutor;
33import org.onlab.util.PredictableExecutor.PickyCallable;
Saurav Das7bcbe702017-06-13 15:35:54 -070034import org.onosproject.cluster.NodeId;
Saurav Das201762d2018-04-21 17:19:48 -070035import org.onosproject.mastership.MastershipEvent;
Charles Chan93e71ba2016-04-29 14:38:22 -070036import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070037import org.onosproject.net.Device;
38import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070039import org.onosproject.net.Link;
Charles Chan2fde6d42017-08-23 14:46:43 -070040import org.onosproject.net.PortNumber;
Charles Chan9797ebb2020-02-14 13:23:57 -080041import org.onosproject.net.flowobjective.Objective;
Charles Chan0b4e6182015-11-03 10:42:14 -080042import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
43import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070044import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Charles Chan2ff1bac2018-03-29 16:03:41 -070045import org.onosproject.store.serializers.KryoNamespaces;
pier41d389a2020-01-07 15:39:39 +010046import org.onosproject.store.service.ConsistentMultimap;
Charles Chan2ff1bac2018-03-29 16:03:41 -070047import org.onosproject.store.service.Serializer;
sanghob35a6192015-04-01 13:05:26 -070048import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
50
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070051import java.time.Instant;
sanghob35a6192015-04-01 13:05:26 -070052import java.util.ArrayList;
Charles Chan2ff1bac2018-03-29 16:03:41 -070053import java.util.Collections;
sanghob35a6192015-04-01 13:05:26 -070054import java.util.HashMap;
55import java.util.HashSet;
Saurav Das7bcbe702017-06-13 15:35:54 -070056import java.util.Iterator;
Charles Chan2ff1bac2018-03-29 16:03:41 -070057import java.util.List;
Saurav Das7bcbe702017-06-13 15:35:54 -070058import java.util.Map;
pier41d389a2020-01-07 15:39:39 +010059import java.util.Map.Entry;
Saurav Dasd2fded02016-12-02 15:43:47 -080060import java.util.Objects;
Charles Chanba6c5752018-04-02 11:46:38 -070061import java.util.Optional;
sanghob35a6192015-04-01 13:05:26 -070062import java.util.Set;
piere08595d2019-04-24 16:12:47 +020063import java.util.concurrent.CompletableFuture;
64import java.util.concurrent.ExecutionException;
65import java.util.concurrent.ExecutorService;
66import java.util.concurrent.Future;
Saurav Das59232cf2016-04-27 18:35:50 -070067import java.util.concurrent.ScheduledExecutorService;
68import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090069import java.util.concurrent.locks.Lock;
70import java.util.concurrent.locks.ReentrantLock;
Charles Chan411beb22019-04-17 14:20:26 -070071import java.util.stream.Collectors;
Saurav Das604ab3a2018-03-18 21:28:15 -070072import java.util.stream.Stream;
73
Pier Ventree0ae7a32016-11-23 09:57:42 -080074import static com.google.common.base.Preconditions.checkNotNull;
75import static java.util.concurrent.Executors.newScheduledThreadPool;
76import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070077
Charles Chane849c192016-01-11 18:28:54 -080078/**
79 * Default routing handler that is responsible for route computing and
80 * routing rule population.
81 */
sanghob35a6192015-04-01 13:05:26 -070082public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080083 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey3717e602018-02-01 13:49:47 -080084 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Das018605f2017-02-18 14:05:44 -080085 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070086 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das201762d2018-04-21 17:19:48 -070087 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Saurav Das137f27f2018-06-11 17:02:31 -070088 private static final long PURGE_DELAY = 1000; // ms
Charles Chan93e71ba2016-04-29 14:38:22 -070089 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070090
91 private SegmentRoutingManager srManager;
92 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053093 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
94 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070095 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090096 private final Lock statusLock = new ReentrantLock();
97 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070098 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080099 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Dasc6ff8f02018-04-23 18:42:12 -0700100 private ScheduledExecutorService executorServiceMstChg
101 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
Saurav Das137f27f2018-06-11 17:02:31 -0700102 private ScheduledExecutorService executorServiceFRR
103 = newScheduledThreadPool(1, groupedThreads("fullRR", "fullRR-%d", log));
piere08595d2019-04-24 16:12:47 +0200104 // Route populators - 0 will leverage available processors
105 private static final int DEFAULT_THREADS = 0;
106 private ExecutorService routePopulators;
Saurav Dasc6ff8f02018-04-23 18:42:12 -0700107
Saurav Das201762d2018-04-21 17:19:48 -0700108 private Instant lastRoutingChange = Instant.EPOCH;
Saurav Das137f27f2018-06-11 17:02:31 -0700109 private Instant lastFullReroute = Instant.EPOCH;
sanghob35a6192015-04-01 13:05:26 -0700110
Saurav Das201762d2018-04-21 17:19:48 -0700111 // Distributed store to keep track of ONOS instance that should program the
112 // device pair. There should be only one instance (the king) that programs the same pair.
Charles Chan2ff1bac2018-03-29 16:03:41 -0700113 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chan50bb6ef2018-04-18 18:41:05 -0700114 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chan2ff1bac2018-03-29 16:03:41 -0700115
pier41d389a2020-01-07 15:39:39 +0100116 // Distributed routes store to keep track of the routes already seen
117 // destination device is the key and target sw is the value
118 ConsistentMultimap<DeviceId, DeviceId> seenBeforeRoutes;
119
Saurav Das201762d2018-04-21 17:19:48 -0700120 // Local store to keep track of all devices that this instance was responsible
121 // for programming in the last run. Helps to determine if mastership changed
122 // during a run - only relevant for programming as a result of topo change.
123 Set<DeviceId> lastProgrammed;
124
sanghob35a6192015-04-01 13:05:26 -0700125 /**
126 * Represents the default routing population status.
127 */
128 public enum Status {
129 // population process is not started yet.
130 IDLE,
sanghob35a6192015-04-01 13:05:26 -0700131 // population process started.
132 STARTED,
piere08595d2019-04-24 16:12:47 +0200133 // population process was aborted due to errors, mostly for groups not found.
sanghob35a6192015-04-01 13:05:26 -0700134 ABORTED,
sanghob35a6192015-04-01 13:05:26 -0700135 // population process was finished successfully.
136 SUCCEEDED
137 }
138
139 /**
140 * Creates a DefaultRoutingHandler object.
141 *
142 * @param srManager SegmentRoutingManager object
143 */
Charles Chan2ff1bac2018-03-29 16:03:41 -0700144 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700145 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
146 .withName("sr-should-program")
147 .withSerializer(Serializer.using(KryoNamespaces.API))
148 .withRelaxedReadConsistency()
149 .build().asJavaMap();
pier41d389a2020-01-07 15:39:39 +0100150 this.seenBeforeRoutes = srManager.storageService.<DeviceId, DeviceId>consistentMultimapBuilder()
151 .withName("programmed-routes")
152 .withSerializer(Serializer.using(KryoNamespaces.API))
153 .withRelaxedReadConsistency()
154 .build();
Charles Chan50bb6ef2018-04-18 18:41:05 -0700155 this.shouldProgramCache = Maps.newConcurrentMap();
156 update(srManager);
piere08595d2019-04-24 16:12:47 +0200157 this.routePopulators = new PredictableExecutor(DEFAULT_THREADS,
158 groupedThreads("onos/sr", "r-populator-%d", log));
Charles Chan50bb6ef2018-04-18 18:41:05 -0700159 }
160
161 /**
162 * Updates a DefaultRoutingHandler object.
163 *
164 * @param srManager SegmentRoutingManager object
165 */
166 void update(SegmentRoutingManager srManager) {
sanghob35a6192015-04-01 13:05:26 -0700167 this.srManager = srManager;
168 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700169 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700170 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700171 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das201762d2018-04-21 17:19:48 -0700172 this.lastProgrammed = Sets.newConcurrentHashSet();
sanghob35a6192015-04-01 13:05:26 -0700173 }
174
175 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700176 * Returns an immutable copy of the current ECMP shortest-path graph as
177 * computed by this controller instance.
178 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700179 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700180 */
181 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
182 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
183 currentEcmpSpgMap.entrySet().forEach(entry -> {
184 if (entry.getValue() != null) {
185 builder.put(entry.getKey(), entry.getValue());
186 }
187 });
188 return builder.build();
189 }
190
Saurav Dasceccf242017-08-03 18:30:35 -0700191 /**
192 * Acquires the lock used when making routing changes.
193 */
194 public void acquireRoutingLock() {
195 statusLock.lock();
196 }
197
198 /**
199 * Releases the lock used when making routing changes.
200 */
201 public void releaseRoutingLock() {
202 statusLock.unlock();
203 }
204
205 /**
206 * Determines if routing in the network has been stable in the last
Charles Chan9797ebb2020-02-14 13:23:57 -0800207 * STABILITY_THRESHOLD seconds, by comparing the current time to the last
Saurav Dasceccf242017-08-03 18:30:35 -0700208 * routing change timestamp.
209 *
210 * @return true if stable
211 */
212 public boolean isRoutingStable() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700213 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
214 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700215 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700216 return (now - last) > STABLITY_THRESHOLD;
217 }
218
Saurav Dasc6ff8f02018-04-23 18:42:12 -0700219 /**
220 * Gracefully shuts down the defaultRoutingHandler. Typically called when
221 * the app is deactivated
222 */
223 public void shutdown() {
224 executorService.shutdown();
225 executorServiceMstChg.shutdown();
Saurav Das137f27f2018-06-11 17:02:31 -0700226 executorServiceFRR.shutdown();
piere08595d2019-04-24 16:12:47 +0200227 routePopulators.shutdown();
Saurav Dasc6ff8f02018-04-23 18:42:12 -0700228 }
Saurav Dasceccf242017-08-03 18:30:35 -0700229
Saurav Das7bcbe702017-06-13 15:35:54 -0700230 //////////////////////////////////////
231 // Route path handling
232 //////////////////////////////////////
233
Saurav Das45f48152018-01-18 12:07:33 -0800234 /* The following three methods represent the three major ways in which
235 * route-path handling is triggered in the network
Saurav Das7bcbe702017-06-13 15:35:54 -0700236 * a) due to configuration change
237 * b) due to route-added event
238 * c) due to change in the topology
239 */
240
Saurav Dasc88d4662017-05-15 15:34:25 -0700241 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700242 * Populates all routing rules to all switches. Typically triggered at
243 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700244 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700245 public void populateAllRoutingRules() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700246 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900247 statusLock.lock();
248 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700249 if (populationStatus == Status.STARTED) {
250 log.warn("Previous rule population is not finished. Cannot"
251 + " proceed with populateAllRoutingRules");
252 return;
253 }
254
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900255 populationStatus = Status.STARTED;
256 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700257 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900258 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700259
Saurav Das7bcbe702017-06-13 15:35:54 -0700260 // take a snapshot of the topology
261 updatedEcmpSpgMap = new HashMap<>();
262 Set<EdgePair> edgePairs = new HashSet<>();
263 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800264 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700265 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800266 new EcmpShortestPathGraph(dstSw, srManager);
267 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700268 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
269 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700270 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700271 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
272 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
273 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700274 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700275
276 if (!shouldProgram(dstSw)) {
Saurav Das201762d2018-04-21 17:19:48 -0700277 lastProgrammed.remove(dstSw);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900278 continue;
Saurav Das201762d2018-04-21 17:19:48 -0700279 } else {
280 lastProgrammed.add(dstSw);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900281 }
Saurav Das201762d2018-04-21 17:19:48 -0700282 // To do a full reroute, assume all route-paths have changed
Charles Chan2ff1bac2018-03-29 16:03:41 -0700283 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800284 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
285 if (targetSw.equals(dev)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700286 continue;
287 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800288 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700289 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900290 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700291 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900292
pier41d389a2020-01-07 15:39:39 +0100293 log.debug("seenBeforeRoutes size {}", seenBeforeRoutes.size());
Saurav Das7bcbe702017-06-13 15:35:54 -0700294 if (!redoRouting(routeChanges, edgePairs, null)) {
295 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
296 populationStatus = Status.ABORTED;
297 log.warn("Failed to repopulate all routing rules.");
298 return;
sanghob35a6192015-04-01 13:05:26 -0700299 }
300
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900301 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
302 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700303 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900304 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700305 return;
pier604e2312019-04-19 20:55:53 +0200306 } catch (Exception e) {
307 log.error("populateAllRoutingRules thrown an exception: {}",
308 e.getMessage(), e);
309 populationStatus = Status.ABORTED;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900310 } finally {
311 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700312 }
sanghob35a6192015-04-01 13:05:26 -0700313 }
314
sangho20eff1d2015-04-13 15:15:58 -0700315 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700316 * Populate rules from all other edge devices to the connect-point(s)
317 * specified for the given subnets.
318 *
319 * @param cpts connect point(s) of the subnets being added
320 * @param subnets subnets being added
Charles Chan2fde6d42017-08-23 14:46:43 -0700321 */
322 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700323 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan71e64f12017-09-11 15:21:57 -0700324 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
325 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
326 return;
327 }
328
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700329 lastRoutingChange = Instant.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700330 statusLock.lock();
331 try {
332 if (populationStatus == Status.STARTED) {
333 log.warn("Previous rule population is not finished. Cannot"
334 + " proceed with routing rules for added routes");
335 return;
336 }
337 populationStatus = Status.STARTED;
338 rulePopulator.resetCounter();
Charles Chan2fde6d42017-08-23 14:46:43 -0700339 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
340 subnets, cpts);
Saurav Dasc568c342018-01-25 09:49:01 -0800341 // In principle an update to a subnet/prefix should not require a
342 // new ECMPspg calculation as it is not a topology event. As a
343 // result, we use the current/existing ECMPspg in the updated map
344 // used by the redoRouting method.
Saurav Das15a81782018-02-09 09:15:03 -0800345 if (updatedEcmpSpgMap == null) {
346 updatedEcmpSpgMap = new HashMap<>();
347 }
Saurav Dasc568c342018-01-25 09:49:01 -0800348 currentEcmpSpgMap.entrySet().forEach(entry -> {
349 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase7f51012018-02-09 17:26:45 -0800350 if (log.isTraceEnabled()) {
351 log.trace("Root switch: {}", entry.getKey());
352 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Dasc568c342018-01-25 09:49:01 -0800353 }
354 });
pier41d389a2020-01-07 15:39:39 +0100355 log.debug("seenBeforeRoutes size {}", seenBeforeRoutes.size());
Saurav Das7bcbe702017-06-13 15:35:54 -0700356 Set<EdgePair> edgePairs = new HashSet<>();
357 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
358 boolean handleRouting = false;
359
360 if (cpts.size() == 2) {
361 // ensure connect points are edge-pairs
362 Iterator<ConnectPoint> iter = cpts.iterator();
363 DeviceId dev1 = iter.next().deviceId();
Charles Chanba6c5752018-04-02 11:46:38 -0700364 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
365 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
366 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700367 } else {
368 log.warn("Connectpoints {} for subnets {} not on "
369 + "pair-devices.. aborting populateSubnet", cpts, subnets);
370 populationStatus = Status.ABORTED;
371 return;
372 }
373 for (ConnectPoint cp : cpts) {
Saurav Dasc568c342018-01-25 09:49:01 -0800374 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
375 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700376 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800377 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
378 log.warn("populateSubnet: no updated graph for dev:{}"
379 + " ... creating", cp.deviceId());
380 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700381 if (!shouldProgram(cp.deviceId())) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700382 continue;
383 }
384 handleRouting = true;
385 }
386 } else {
387 // single connect point
388 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Dasc568c342018-01-25 09:49:01 -0800389 if (updatedEcmpSpgMap.get(dstSw) == null) {
390 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700391 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800392 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
393 log.warn("populateSubnet: no updated graph for dev:{}"
394 + " ... creating", dstSw);
395 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700396 handleRouting = shouldProgram(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700397 }
398
399 if (!handleRouting) {
400 log.debug("This instance is not handling ecmp routing to the "
401 + "connectPoint(s) {}", cpts);
402 populationStatus = Status.ABORTED;
403 return;
404 }
405
406 // if it gets here, this instance should handle routing for the
407 // connectpoint(s). Assume all route-paths have to be updated to
408 // the connectpoint(s) with the following exceptions
409 // 1. if target is non-edge no need for routing rules
410 // 2. if target is one of the connectpoints
411 for (ConnectPoint cp : cpts) {
412 DeviceId dstSw = cp.deviceId();
413 for (Device targetSw : srManager.deviceService.getDevices()) {
414 boolean isEdge = false;
415 try {
416 isEdge = config.isEdgeDevice(targetSw.id());
417 } catch (DeviceConfigNotFoundException e) {
Charles Chan92726132018-02-16 17:20:54 -0800418 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
419 continue;
Saurav Das7bcbe702017-06-13 15:35:54 -0700420 }
Charles Chanba6c5752018-04-02 11:46:38 -0700421 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700422 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chanba6c5752018-04-02 11:46:38 -0700423 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700424 continue;
425 }
426 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
427 }
428 }
429
430 if (!redoRouting(routeChanges, edgePairs, subnets)) {
431 log.debug("populateSubnet: populationStatus is ABORTED");
432 populationStatus = Status.ABORTED;
433 log.warn("Failed to repopulate the rules for subnet.");
434 return;
435 }
436
437 log.debug("populateSubnet: populationStatus is SUCCEEDED");
438 populationStatus = Status.SUCCEEDED;
439 log.info("Completed subnet population. Total # of rules pushed : {}",
440 rulePopulator.getCounter());
441 return;
442
pier604e2312019-04-19 20:55:53 +0200443 } catch (Exception e) {
444 log.error("populateSubnet thrown an exception: {}",
445 e.getMessage(), e);
446 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700447 } finally {
448 statusLock.unlock();
449 }
450 }
451
452 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700453 * Populates the routing rules or makes hash group changes according to the
454 * route-path changes due to link failure, switch failure or link up. This
455 * method should only be called for one of these three possible event-types.
Saurav Das604ab3a2018-03-18 21:28:15 -0700456 * Note that when a switch goes away, all of its links fail as well, but
457 * this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700458 *
Saurav Das604ab3a2018-03-18 21:28:15 -0700459 * @param linkDown the single failed link, or null for other conditions such
460 * as link-up or a removed switch
Saurav Dasc88d4662017-05-15 15:34:25 -0700461 * @param linkUp the single link up, or null for other conditions such as
Saurav Das604ab3a2018-03-18 21:28:15 -0700462 * link-down or a removed switch
463 * @param switchDown the removed switch, or null for other conditions such
464 * as link-down or link-up
465 * @param seenBefore true if this event is for a linkUp or linkDown for a
466 * seen link
467 */
468 // TODO This method should be refactored into three separated methods
Charles Chan5adc6282018-06-19 20:56:33 -0700469 public void populateRoutingRulesForLinkStatusChange(Link linkDown, Link linkUp,
470 DeviceId switchDown, boolean seenBefore) {
Saurav Das604ab3a2018-03-18 21:28:15 -0700471 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
472 .count() != 1) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700473 log.warn("Only one event can be handled for link status change .. aborting");
474 return;
475 }
Saurav Das604ab3a2018-03-18 21:28:15 -0700476
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700477 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900478 statusLock.lock();
479 try {
sangho20eff1d2015-04-13 15:15:58 -0700480
481 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700482 log.warn("Previous rule population is not finished. Cannot"
Saurav Dasc568c342018-01-25 09:49:01 -0800483 + " proceeed with routingRules for Topology change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700484 return;
sangho20eff1d2015-04-13 15:15:58 -0700485 }
486
Saurav Das7bcbe702017-06-13 15:35:54 -0700487 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700488 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700489 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700490 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530491 EcmpShortestPathGraph ecmpSpgUpdated =
492 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700493 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700494 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
495 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700496 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700497 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
498 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
499 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700500 }
sangho45b009c2015-05-07 13:30:57 -0700501 }
502
Saurav Dasc568c342018-01-25 09:49:01 -0800503 log.info("Starting to populate routing rules from Topology change");
sangho52abe3a2015-05-05 14:13:34 -0700504
sangho20eff1d2015-04-13 15:15:58 -0700505 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700506 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700507 + "populationStatus is STARTED");
pier41d389a2020-01-07 15:39:39 +0100508 log.debug("seenBeforeRoutes size {}", seenBeforeRoutes.size());
sangho20eff1d2015-04-13 15:15:58 -0700509 populationStatus = Status.STARTED;
Saurav Dasc568c342018-01-25 09:49:01 -0800510 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
511 boolean hashGroupsChanged = false;
Saurav Das4e3224f2016-11-29 14:27:25 -0800512 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700513 if (linkDown == null) {
514 // either a linkUp or a switchDown - compute all route changes by
515 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dase0d4c872018-03-05 14:37:16 -0800516 routeChanges = computeRouteChange(switchDown);
Saurav Dasc88d4662017-05-15 15:34:25 -0700517
pier52517ff2019-04-25 18:51:51 +0200518 // deal with linkUp
519 if (linkUp != null) {
520 // deal with linkUp of a seen-before link
521 if (seenBefore) {
522 // link previously seen before
523 // do hash-bucket changes instead of a re-route
524 processHashGroupChangeForLinkUp(routeChanges);
525 // clear out routesChanges so a re-route is not attempted
526 routeChanges = ImmutableSet.of();
527 hashGroupsChanged = true;
528 } else {
529 // do hash-bucket changes first, method will return changed routes;
530 // for each route not changed it will perform a reroute
531 Set<ArrayList<DeviceId>> changedRoutes = processHashGroupChangeForLinkUp(routeChanges);
532 Set<ArrayList<DeviceId>> routeChangesTemp = getExpandedRoutes(routeChanges);
533 changedRoutes.forEach(routeChangesTemp::remove);
534 // if routesChanges is empty a re-route is not attempted
535 routeChanges = routeChangesTemp;
536 for (ArrayList<DeviceId> route : routeChanges) {
537 log.debug("remaining routes Target -> Root");
538 if (route.size() == 1) {
539 log.debug(" : all -> {}", route.get(0));
540 } else {
541 log.debug(" : {} -> {}", route.get(0), route.get(1));
542 }
543 }
544 // Mark hash groups as changed
545 if (!changedRoutes.isEmpty()) {
546 hashGroupsChanged = true;
547 }
548 }
549
Saurav Dasc88d4662017-05-15 15:34:25 -0700550 }
551
Saurav Das9df5b7c2017-08-14 16:44:43 -0700552 //deal with switchDown
553 if (switchDown != null) {
pier52517ff2019-04-25 18:51:51 +0200554 processHashGroupChangeForFailure(routeChanges, switchDown);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700555 // clear out routesChanges so a re-route is not attempted
556 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800557 hashGroupsChanged = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700558 }
sangho20eff1d2015-04-13 15:15:58 -0700559 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700560 // link has gone down
561 // Compare existing ECMP SPG only with the link that went down
562 routeChanges = computeDamagedRoutes(linkDown);
pier52517ff2019-04-25 18:51:51 +0200563 processHashGroupChangeForFailure(routeChanges, null);
Saurav Das137f27f2018-06-11 17:02:31 -0700564 // clear out routesChanges so a re-route is not attempted
565 routeChanges = ImmutableSet.of();
566 hashGroupsChanged = true;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700567 }
568
sangho20eff1d2015-04-13 15:15:58 -0700569 if (routeChanges.isEmpty()) {
Saurav Dasc568c342018-01-25 09:49:01 -0800570 if (hashGroupsChanged) {
571 log.info("Hash-groups changed for link status change");
572 } else {
573 log.info("No re-route or re-hash attempted for the link"
574 + " status change");
575 updatedEcmpSpgMap.keySet().forEach(devId -> {
576 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
577 log.debug("Updating ECMPspg for remaining dev:{}", devId);
578 });
579 }
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700580 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700581 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700582 return;
sangho20eff1d2015-04-13 15:15:58 -0700583 }
584
pier52517ff2019-04-25 18:51:51 +0200585 if (hashGroupsChanged) {
586 log.debug("Hash-groups changed for link status change");
587 }
588
Saurav Dasc88d4662017-05-15 15:34:25 -0700589 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700590 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700591 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700592 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700593 log.info("Completed repopulation of rules for link-status change."
594 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700595 return;
sangho20eff1d2015-04-13 15:15:58 -0700596 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700597 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700598 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700599 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700600 return;
sangho20eff1d2015-04-13 15:15:58 -0700601 }
pier604e2312019-04-19 20:55:53 +0200602 } catch (Exception e) {
603 log.error("populateRoutingRulesForLinkStatusChange thrown an exception: {}",
604 e.getMessage(), e);
605 populationStatus = Status.ABORTED;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900606 } finally {
607 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700608 }
609 }
610
Saurav Dasc88d4662017-05-15 15:34:25 -0700611 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700612 * Processes a set a route-path changes by reprogramming routing rules and
613 * creating new hash-groups or editing them if necessary. This method also
614 * determines the next-hops for the route-path from the src-switch (target)
615 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700616 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700617 * @param routeChanges 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 * @param edgePairs a set of edge-switches that are paired by configuration
622 * @param subnets a set of prefixes that need to be populated in the routing
623 * table of the target switch in the route-path. Can be null,
624 * in which case all the prefixes belonging to the dst-switch
625 * will be populated in the target switch
626 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700627 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700628 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
629 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
630 // first make every entry two-elements
pier52517ff2019-04-25 18:51:51 +0200631 Set<ArrayList<DeviceId>> changedRoutes = getExpandedRoutes(routeChanges);
632 // no valid routes - fail fast
633 if (changedRoutes.isEmpty()) {
634 return false;
Saurav Das7bcbe702017-06-13 15:35:54 -0700635 }
636
pier41d389a2020-01-07 15:39:39 +0100637 // Temporary stores the changed routes
638 Set<ArrayList<DeviceId>> tempRoutes = ImmutableSet.copyOf(changedRoutes);
Saurav Das7bcbe702017-06-13 15:35:54 -0700639 // now process changedRoutes according to edgePairs
640 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
641 return false; //abort routing and fail fast
642 }
pier41d389a2020-01-07 15:39:39 +0100643 // Calculate the programmed routes pointing to the pairs
644 Set<ArrayList<DeviceId>> programmedPairRoutes = Sets.difference(tempRoutes, changedRoutes);
645 log.debug("Evaluating programmed pair routes");
646 storeSeenBeforeRoutes(programmedPairRoutes);
Saurav Das7bcbe702017-06-13 15:35:54 -0700647
pier41d389a2020-01-07 15:39:39 +0100648 // Temporary stores the left routes
649 tempRoutes = ImmutableSet.copyOf(changedRoutes);
Saurav Das7bcbe702017-06-13 15:35:54 -0700650 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Dasc568c342018-01-25 09:49:01 -0800651 Set<DeviceId> updatedDevices = Sets.newHashSet();
652 if (!redoRoutingIndividualDests(subnets, changedRoutes,
653 updatedDevices)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700654 return false; //abort routing and fail fast
655 }
pier41d389a2020-01-07 15:39:39 +0100656 // Calculate the individual programmed routes
657 Set<ArrayList<DeviceId>> programmedIndividualRoutes = Sets.difference(tempRoutes, changedRoutes);
658 log.debug("Evaluating individual programmed routes");
659 storeSeenBeforeRoutes(programmedIndividualRoutes);
Saurav Das7bcbe702017-06-13 15:35:54 -0700660
Saurav Das7bcbe702017-06-13 15:35:54 -0700661 // update ecmpSPG for all edge-pairs
662 for (EdgePair ep : edgePairs) {
663 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
664 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
665 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
666 }
Saurav Dasc568c342018-01-25 09:49:01 -0800667
668 // here is where we update all devices not touched by this instance
669 updatedEcmpSpgMap.keySet().stream()
670 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
671 .filter(devId -> !updatedDevices.contains(devId))
672 .forEach(devId -> {
673 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
674 log.debug("Updating ECMPspg for remaining dev:{}", devId);
675 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700676 return true;
677 }
678
679 /**
pier41d389a2020-01-07 15:39:39 +0100680 * Stores the routes seen before. Routes are two-elements arrays.
681 * @param seenRoutes seen before routes
682 */
683 private void storeSeenBeforeRoutes(Set<ArrayList<DeviceId>> seenRoutes) {
684 Set<DeviceId> nextHops;
685 for (ArrayList<DeviceId> route : seenRoutes) {
686 log.debug("Route {} -> {} has been programmed", route.get(0), route.get(1));
687 nextHops = getNextHops(route.get(0), route.get(1));
688 // No valid next hops - cannot be considered a programmed route
689 if (nextHops.isEmpty()) {
690 log.debug("Could not find next hop from target:{} --> dst {} "
691 + "skipping this route", route.get(0), route.get(1));
692 continue;
693 }
694 // Already present - do not add again
695 if (seenBeforeRoutes.containsEntry(route.get(1), route.get(0))) {
696 log.debug("Route from target:{} --> dst {} " +
697 "already present, skipping this route", route.get(0), route.get(1));
698 continue;
699 }
700 seenBeforeRoutes.put(route.get(1), route.get(0));
701 }
702 }
703
704 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700705 * Programs targetSw in the changedRoutes for given prefixes reachable by
706 * an edgePair. If no prefixes are given, the method will use configured
707 * subnets/prefixes. If some configured subnets belong only to a specific
708 * destination in the edgePair, then the target switch will be programmed
709 * only to that destination.
710 *
711 * @param edgePairs set of edge-pairs for which target will be programmed
712 * @param subnets a set of prefixes that need to be populated in the routing
713 * table of the target switch in the changedRoutes. Can be null,
714 * in which case all the configured prefixes belonging to the
715 * paired switches will be populated in the target switch
716 * @param changedRoutes a set of route-path changes, where each route-path is
717 * a list with its first element the src-switch (target)
718 * of the path, and the second element the dst-switch of
719 * the path.
720 * @return true if successful
721 */
piere08595d2019-04-24 16:12:47 +0200722 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs, Set<IpPrefix> subnets,
723 Set<ArrayList<DeviceId>> changedRoutes) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700724 for (EdgePair ep : edgePairs) {
725 // temp store for a target's changedRoutes to this edge-pair
726 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
727 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
728 while (i.hasNext()) {
729 ArrayList<DeviceId> route = i.next();
730 DeviceId dstSw = route.get(1);
731 if (ep.includes(dstSw)) {
732 // routeChange for edge pair found
733 // sort by target iff target is edge and remove from changedRoutes
734 DeviceId targetSw = route.get(0);
735 try {
736 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
737 continue;
738 }
739 } catch (DeviceConfigNotFoundException e) {
740 log.warn(e.getMessage() + "aborting redoRouting");
741 return false;
742 }
743 // route is from another edge to this edge-pair
744 if (targetRoutes.containsKey(targetSw)) {
745 targetRoutes.get(targetSw).add(route);
746 } else {
747 Set<ArrayList<DeviceId>> temp = new HashSet<>();
748 temp.add(route);
749 targetRoutes.put(targetSw, temp);
750 }
751 i.remove();
752 }
753 }
754 // so now for this edgepair we have a per target set of routechanges
755 // process target->edgePair route
piere08595d2019-04-24 16:12:47 +0200756 List<Future<Boolean>> futures = Lists.newArrayList();
pier41d389a2020-01-07 15:39:39 +0100757 for (Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
Saurav Das7bcbe702017-06-13 15:35:54 -0700758 targetRoutes.entrySet()) {
759 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
760 entry.getKey(), ep);
piere08595d2019-04-24 16:12:47 +0200761 futures.add(routePopulators.submit(new RedoRoutingEdgePair(entry.getKey(), entry.getValue(),
762 subnets, ep)));
763 }
764 if (!checkJobs(futures)) {
765 return false;
Saurav Das7bcbe702017-06-13 15:35:54 -0700766 }
767 // if it gets here it has succeeded for all targets to this edge-pair
768 }
769 return true;
770 }
771
piere08595d2019-04-24 16:12:47 +0200772 private final class RedoRoutingEdgePair implements PickyCallable<Boolean> {
773 private DeviceId targetSw;
774 private Set<ArrayList<DeviceId>> routes;
775 private Set<IpPrefix> subnets;
776 private EdgePair ep;
777
778 /**
779 * Builds a RedoRoutingEdgePair task which provides a result.
780 *
781 * @param targetSw the target switch
782 * @param routes the changed routes
783 * @param subnets the subnets
784 * @param ep the edge pair
785 */
786 RedoRoutingEdgePair(DeviceId targetSw, Set<ArrayList<DeviceId>> routes,
787 Set<IpPrefix> subnets, EdgePair ep) {
788 this.targetSw = targetSw;
789 this.routes = routes;
790 this.subnets = subnets;
791 this.ep = ep;
792 }
793
794 @Override
795 public Boolean call() throws Exception {
796 return redoRoutingEdgePair();
797 }
798
799 @Override
800 public int hint() {
801 return targetSw.hashCode();
802 }
803
804 private boolean redoRoutingEdgePair() {
805 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
806 routes.forEach(route -> {
807 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
808 log.debug("route: target {} -> dst {} found with next-hops {}",
809 route.get(0), route.get(1), nhops);
810 perDstNextHops.put(route.get(1), nhops);
811 });
812
813 List<Set<IpPrefix>> batchedSubnetDev1, batchedSubnetDev2;
814 if (subnets != null) {
815 batchedSubnetDev1 = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
816 batchedSubnetDev2 = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
817 } else {
818 batchedSubnetDev1 = config.getBatchedSubnets(ep.dev1);
819 batchedSubnetDev2 = config.getBatchedSubnets(ep.dev2);
820 }
821 List<Set<IpPrefix>> batchedSubnetBoth = Streams
822 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.intersection(a, b))
823 .filter(set -> !set.isEmpty())
824 .collect(Collectors.toList());
825 List<Set<IpPrefix>> batchedSubnetDev1Only = Streams
826 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.difference(a, b))
827 .filter(set -> !set.isEmpty())
828 .collect(Collectors.toList());
829 List<Set<IpPrefix>> batchedSubnetDev2Only = Streams
830 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.difference(b, a))
831 .filter(set -> !set.isEmpty())
832 .collect(Collectors.toList());
833
834 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
835 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
836
837 // handle routing to subnets common to edge-pair
838 // only if the targetSw is not part of the edge-pair and there
839 // exists a next hop to at least one of the devices in the edge-pair
840 if (!ep.includes(targetSw)
841 && ((nhDev1 != null && !nhDev1.isEmpty()) || (nhDev2 != null && !nhDev2.isEmpty()))) {
842 log.trace("getSubnets on both {} and {}: {}", ep.dev1, ep.dev2, batchedSubnetBoth);
843 for (Set<IpPrefix> prefixes : batchedSubnetBoth) {
844 if (!populateEcmpRoutingRulePartial(targetSw, ep.dev1, ep.dev2,
845 perDstNextHops, prefixes)) {
846 return false; // abort everything and fail fast
847 }
848 }
849
850 }
851 // handle routing to subnets that only belong to dev1 only if
852 // a next-hop exists from the target to dev1
853 if (!batchedSubnetDev1Only.isEmpty() &&
854 batchedSubnetDev1Only.stream().anyMatch(subnet -> !subnet.isEmpty()) &&
855 nhDev1 != null && !nhDev1.isEmpty()) {
856 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
857 onlyDev1NextHops.put(ep.dev1, nhDev1);
858 log.trace("getSubnets on {} only: {}", ep.dev1, batchedSubnetDev1Only);
859 for (Set<IpPrefix> prefixes : batchedSubnetDev1Only) {
860 if (!populateEcmpRoutingRulePartial(targetSw, ep.dev1, null,
861 onlyDev1NextHops, prefixes)) {
862 return false; // abort everything and fail fast
863 }
864 }
865 }
866 // handle routing to subnets that only belong to dev2 only if
867 // a next-hop exists from the target to dev2
868 if (!batchedSubnetDev2Only.isEmpty() &&
869 batchedSubnetDev2Only.stream().anyMatch(subnet -> !subnet.isEmpty()) &&
870 nhDev2 != null && !nhDev2.isEmpty()) {
871 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
872 onlyDev2NextHops.put(ep.dev2, nhDev2);
873 log.trace("getSubnets on {} only: {}", ep.dev2, batchedSubnetDev2Only);
874 for (Set<IpPrefix> prefixes : batchedSubnetDev2Only) {
875 if (!populateEcmpRoutingRulePartial(targetSw, ep.dev2, null,
876 onlyDev2NextHops, prefixes)) {
877 return false; // abort everything and fail fast
878 }
879 }
880 }
881 return true;
882 }
883 }
884
Saurav Das7bcbe702017-06-13 15:35:54 -0700885 /**
886 * Programs targetSw in the changedRoutes for given prefixes reachable by
887 * a destination switch that is not part of an edge-pair.
888 * If no prefixes are given, the method will use configured subnets/prefixes.
889 *
890 * @param subnets a set of prefixes that need to be populated in the routing
891 * table of the target switch in the changedRoutes. Can be null,
892 * in which case all the configured prefixes belonging to the
893 * paired switches will be populated in the target switch
894 * @param changedRoutes a set of route-path changes, where each route-path is
895 * a list with its first element the src-switch (target)
896 * of the path, and the second element the dst-switch of
897 * the path.
898 * @return true if successful
899 */
piere08595d2019-04-24 16:12:47 +0200900 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets, Set<ArrayList<DeviceId>> changedRoutes,
Saurav Dasc568c342018-01-25 09:49:01 -0800901 Set<DeviceId> updatedDevices) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700902 // aggregate route-path changes for each dst device
903 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
904 new HashMap<>();
905 for (ArrayList<DeviceId> route: changedRoutes) {
906 DeviceId dstSw = route.get(1);
907 ArrayList<ArrayList<DeviceId>> deviceRoutes =
908 routesBydevice.get(dstSw);
909 if (deviceRoutes == null) {
910 deviceRoutes = new ArrayList<>();
911 routesBydevice.put(dstSw, deviceRoutes);
912 }
913 deviceRoutes.add(route);
914 }
piere08595d2019-04-24 16:12:47 +0200915 // iterate over the impacted devices
Saurav Das7bcbe702017-06-13 15:35:54 -0700916 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
917 ArrayList<ArrayList<DeviceId>> deviceRoutes =
918 routesBydevice.get(impactedDstDevice);
piere08595d2019-04-24 16:12:47 +0200919 List<Future<Boolean>> futures = Lists.newArrayList();
Saurav Das7bcbe702017-06-13 15:35:54 -0700920 for (ArrayList<DeviceId> route: deviceRoutes) {
921 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
922 route.get(0), route.get(1));
piere08595d2019-04-24 16:12:47 +0200923 futures.add(routePopulators.submit(new RedoRoutingIndividualDest(subnets, route)));
pier41d389a2020-01-07 15:39:39 +0100924 changedRoutes.remove(route);
piere08595d2019-04-24 16:12:47 +0200925 }
926 // check the execution of each job
927 if (!checkJobs(futures)) {
928 return false;
Saurav Das7bcbe702017-06-13 15:35:54 -0700929 }
930 //Only if all the flows for all impacted routes to a
931 //specific target are pushed successfully, update the
932 //ECMP graph for that target. Or else the next event
933 //would not see any changes in the ECMP graphs.
934 //In another case, the target switch has gone away, so
935 //routes can't be installed. In that case, the current map
936 //is updated here, without any flows being pushed.
937 currentEcmpSpgMap.put(impactedDstDevice,
938 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Dasc568c342018-01-25 09:49:01 -0800939 updatedDevices.add(impactedDstDevice);
Saurav Das7bcbe702017-06-13 15:35:54 -0700940 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
941 }
942 return true;
943 }
944
piere08595d2019-04-24 16:12:47 +0200945 private final class RedoRoutingIndividualDest implements PickyCallable<Boolean> {
946 private DeviceId targetSw;
947 private ArrayList<DeviceId> route;
948 private Set<IpPrefix> subnets;
949
950 /**
951 * Builds a RedoRoutingIndividualDest task, which provides a result.
952 *
953 * @param subnets a set of prefixes
954 * @param route a route-path change
955 */
956 RedoRoutingIndividualDest(Set<IpPrefix> subnets, ArrayList<DeviceId> route) {
957 this.targetSw = route.get(0);
958 this.route = route;
959 this.subnets = subnets;
960 }
961
962 @Override
963 public Boolean call() throws Exception {
964 DeviceId dstSw = route.get(1); // same as impactedDstDevice
965 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
966 if (nextHops.isEmpty()) {
967 log.debug("Could not find next hop from target:{} --> dst {} "
968 + "skipping this route", targetSw, dstSw);
969 return true;
970 }
971 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
972 nhops.put(dstSw, nextHops);
973 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
974 (subnets == null) ? Sets.newHashSet() : subnets)) {
975 return false; // abort routing and fail fast
976 }
977 log.debug("Populating flow rules from target: {} to dst: {}"
978 + " is successful", targetSw, dstSw);
979 return true;
980 }
981
982 @Override
983 public int hint() {
984 return targetSw.hashCode();
985 }
986 }
987
Saurav Das7bcbe702017-06-13 15:35:54 -0700988 /**
989 * Populate ECMP rules for subnets from target to destination via nexthops.
990 *
991 * @param targetSw Device ID of target switch in which rules will be programmed
992 * @param destSw1 Device ID of final destination switch to which the rules will forward
993 * @param destSw2 Device ID of paired destination switch to which the rules will forward
994 * A null deviceId indicates packets should only be sent to destSw1
Saurav Dasa4020382018-02-14 14:14:54 -0800995 * @param nextHops Map of a set of next hops per destSw
Saurav Das7bcbe702017-06-13 15:35:54 -0700996 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
997 * @return true if it succeeds in populating rules
998 */ // refactor
piere08595d2019-04-24 16:12:47 +0200999 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw, DeviceId destSw1, DeviceId destSw2,
1000 Map<DeviceId, Set<DeviceId>> nextHops, Set<IpPrefix> subnets) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001001 boolean result;
1002 // If both target switch and dest switch are edge routers, then set IP
1003 // rule for both subnet and router IP.
1004 boolean targetIsEdge;
1005 boolean dest1IsEdge;
1006 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
1007 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
1008
1009 try {
1010 targetIsEdge = config.isEdgeDevice(targetSw);
1011 dest1IsEdge = config.isEdgeDevice(destSw1);
1012 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
1013 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
1014 if (destSw2 != null) {
1015 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
1016 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
1017 }
1018 } catch (DeviceConfigNotFoundException e) {
1019 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -07001020 return false;
1021 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001022
1023 if (targetIsEdge && dest1IsEdge) {
Charles Chan411beb22019-04-17 14:20:26 -07001024 List<Set<IpPrefix>> batchedSubnets;
1025 if (subnets != null && !subnets.isEmpty()) {
1026 batchedSubnets = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
1027 } else {
1028 batchedSubnets = config.getBatchedSubnets(destSw1);
1029 }
Saurav Dasa4020382018-02-14 14:14:54 -08001030 // XXX - Rethink this - ignoring routerIPs in all other switches
1031 // even edge to edge switches
Saurav Das7bcbe702017-06-13 15:35:54 -07001032 /*subnets.add(dest1RouterIpv4.toIpPrefix());
1033 if (dest1RouterIpv6 != null) {
1034 subnets.add(dest1RouterIpv6.toIpPrefix());
1035 }
1036 if (destSw2 != null && dest2RouterIpv4 != null) {
1037 subnets.add(dest2RouterIpv4.toIpPrefix());
1038 if (dest2RouterIpv6 != null) {
1039 subnets.add(dest2RouterIpv6.toIpPrefix());
1040 }
1041 }*/
Charles Chan411beb22019-04-17 14:20:26 -07001042 log.trace("getSubnets on {}: {}", destSw1, batchedSubnets);
1043 for (Set<IpPrefix> prefixes : batchedSubnets) {
1044 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
1045 + "for subnets {}", targetSw, destSw1,
1046 (destSw2 != null) ? ("& " + destSw2) : "",
1047 prefixes);
1048 if (!rulePopulator.populateIpRuleForSubnet(targetSw, prefixes, destSw1, destSw2, nextHops)) {
1049 return false;
1050 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001051 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001052 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001053
1054 if (!targetIsEdge && dest1IsEdge) {
1055 // MPLS rules in all non-edge target devices. These rules are for
1056 // individual destinations, even if the dsts are part of edge-pairs.
1057 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
1058 + "all MPLS rules", targetSw, destSw1);
piere08595d2019-04-24 16:12:47 +02001059 result = rulePopulator.populateMplsRule(targetSw, destSw1, nextHops.get(destSw1), dest1RouterIpv4);
Saurav Das7bcbe702017-06-13 15:35:54 -07001060 if (!result) {
1061 return false;
1062 }
1063 if (dest1RouterIpv6 != null) {
Saurav Dasa4020382018-02-14 14:14:54 -08001064 int v4sid = 0, v6sid = 0;
1065 try {
1066 v4sid = config.getIPv4SegmentId(destSw1);
1067 v6sid = config.getIPv6SegmentId(destSw1);
1068 } catch (DeviceConfigNotFoundException e) {
1069 log.warn(e.getMessage());
1070 }
1071 if (v4sid != v6sid) {
piere08595d2019-04-24 16:12:47 +02001072 result = rulePopulator.populateMplsRule(targetSw, destSw1, nextHops.get(destSw1),
Saurav Dasa4020382018-02-14 14:14:54 -08001073 dest1RouterIpv6);
1074 if (!result) {
1075 return false;
1076 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001077 }
1078 }
1079 }
1080
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -07001081 if (!targetIsEdge && !dest1IsEdge) {
1082 // MPLS rules for inter-connected spines
1083 // can be merged with above if, left it here for clarity
1084 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
1085 + "all MPLS rules", targetSw, destSw1);
1086
piere08595d2019-04-24 16:12:47 +02001087 result = rulePopulator.populateMplsRule(targetSw, destSw1, nextHops.get(destSw1), dest1RouterIpv4);
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -07001088 if (!result) {
1089 return false;
1090 }
1091
1092 if (dest1RouterIpv6 != null) {
1093 int v4sid = 0, v6sid = 0;
1094 try {
1095 v4sid = config.getIPv4SegmentId(destSw1);
1096 v6sid = config.getIPv6SegmentId(destSw1);
1097 } catch (DeviceConfigNotFoundException e) {
1098 log.warn(e.getMessage());
1099 }
1100 if (v4sid != v6sid) {
piere08595d2019-04-24 16:12:47 +02001101 result = rulePopulator.populateMplsRule(targetSw, destSw1, nextHops.get(destSw1),
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -07001102 dest1RouterIpv6);
1103 if (!result) {
1104 return false;
1105 }
1106 }
1107 }
1108 }
1109
Saurav Das7bcbe702017-06-13 15:35:54 -07001110 // To save on ECMP groups
1111 // avoid MPLS rules in non-edge-devices to non-edge-devices
1112 // avoid MPLS transit rules in edge-devices
1113 // avoid loopback IP rules in edge-devices to non-edge-devices
1114 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -07001115 }
1116
1117 /**
pier52517ff2019-04-25 18:51:51 +02001118 * Processes a set a route-path changes due to a switch/link failure by editing hash groups.
Saurav Dasc88d4662017-05-15 15:34:25 -07001119 *
1120 * @param routeChanges a set of route-path changes, where each route-path is
1121 * a list with its first element the src-switch of the path
1122 * and the second element the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -07001123 * @param failedSwitch the switchId if the route changes are for a failed switch,
1124 * otherwise null
1125 */
pier52517ff2019-04-25 18:51:51 +02001126 private void processHashGroupChangeForFailure(Set<ArrayList<DeviceId>> routeChanges,
1127 DeviceId failedSwitch) {
Saurav Das9df5b7c2017-08-14 16:44:43 -07001128 // first, ensure each routeChanges entry has two elements
pier52517ff2019-04-25 18:51:51 +02001129 Set<ArrayList<DeviceId>> changedRoutes = getAllExpandedRoutes(routeChanges);
Saurav Dasc568c342018-01-25 09:49:01 -08001130 boolean someFailed = false;
pier52517ff2019-04-25 18:51:51 +02001131 boolean success;
Saurav Dasc568c342018-01-25 09:49:01 -08001132 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Das9df5b7c2017-08-14 16:44:43 -07001133 for (ArrayList<DeviceId> route : changedRoutes) {
1134 DeviceId targetSw = route.get(0);
1135 DeviceId dstSw = route.get(1);
pier52517ff2019-04-25 18:51:51 +02001136 success = fixHashGroupsForRoute(route, true);
1137 // it's possible that we cannot fix hash groups for a route
1138 // if the target switch has failed. Nevertheless the ecmp graph
1139 // for the impacted switch must still be updated.
1140 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
1141 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1142 currentEcmpSpgMap.remove(targetSw);
1143 log.debug("Updating ECMPspg for dst:{} removing failed switch "
1144 + "target:{}", dstSw, targetSw);
1145 updatedDevices.add(targetSw);
1146 updatedDevices.add(dstSw);
1147 continue;
pier41d389a2020-01-07 15:39:39 +01001148
pier52517ff2019-04-25 18:51:51 +02001149 }
1150 //linkfailed - update both sides
1151 if (success) {
1152 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1153 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1154 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1155 + " or switchdown", dstSw, targetSw);
1156 updatedDevices.add(targetSw);
1157 updatedDevices.add(dstSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001158 } else {
pier52517ff2019-04-25 18:51:51 +02001159 someFailed = true;
Saurav Dasc88d4662017-05-15 15:34:25 -07001160 }
1161 }
Saurav Dasc568c342018-01-25 09:49:01 -08001162 if (!someFailed) {
1163 // here is where we update all devices not touched by this instance
1164 updatedEcmpSpgMap.keySet().stream()
1165 .filter(devId -> !updatedDevices.contains(devId))
1166 .forEach(devId -> {
1167 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1168 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1169 });
1170 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001171 }
1172
1173 /**
pier52517ff2019-04-25 18:51:51 +02001174 * Processes a set a route-path changes due to link up by editing hash groups.
1175 *
1176 * @param routeChanges a set of route-path changes, where each route-path is
1177 * a list with its first element the src-switch of the path
1178 * and the second element the dst-switch of the path.
1179 * @return set of changed routes
1180 */
1181 private Set<ArrayList<DeviceId>> processHashGroupChangeForLinkUp(Set<ArrayList<DeviceId>> routeChanges) {
1182 // Stores changed routes
1183 Set<ArrayList<DeviceId>> doneRoutes = new HashSet<>();
1184 // first, ensure each routeChanges entry has two elements
1185 Set<ArrayList<DeviceId>> changedRoutes = getAllExpandedRoutes(routeChanges);
1186 boolean someFailed = false;
1187 boolean success;
1188 Set<DeviceId> updatedDevices = Sets.newHashSet();
1189 for (ArrayList<DeviceId> route : changedRoutes) {
1190 DeviceId targetSw = route.get(0);
1191 DeviceId dstSw = route.get(1);
1192 // linkup - fix (if possible)
1193 success = fixHashGroupsForRoute(route, false);
1194 if (success) {
1195 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1196 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1197 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
1198 targetSw, dstSw);
1199 updatedDevices.add(targetSw);
1200 updatedDevices.add(dstSw);
1201 doneRoutes.add(route);
1202 } else {
1203 someFailed = true;
1204 }
1205
1206 }
1207 if (!someFailed) {
1208 // here is where we update all devices not touched by this instance
1209 updatedEcmpSpgMap.keySet().stream()
1210 .filter(devId -> !updatedDevices.contains(devId))
1211 .forEach(devId -> {
1212 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1213 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1214 });
1215 }
1216 return doneRoutes;
1217 }
1218
1219 /**
Saurav Dasc88d4662017-05-15 15:34:25 -07001220 * Edits hash groups in the src-switch (targetSw) of a route-path by
1221 * calling the groupHandler to either add or remove buckets in an existing
1222 * hash group.
1223 *
1224 * @param route a single list representing a route-path where the first element
1225 * is the src-switch (targetSw) of the route-path and the
1226 * second element is the dst-switch
1227 * @param revoke true if buckets in the hash-groups need to be removed;
1228 * false if buckets in the hash-groups need to be added
1229 * @return true if the hash group editing is successful
1230 */
1231 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1232 boolean revoke) {
1233 DeviceId targetSw = route.get(0);
1234 if (route.size() < 2) {
1235 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1236 return false;
1237 }
1238 DeviceId destSw = route.get(1);
pier41d389a2020-01-07 15:39:39 +01001239 if (!seenBeforeRoutes.containsEntry(destSw, targetSw)) {
1240 log.warn("Cannot fixHashGroupsForRoute {} -> {} has not been programmed before",
1241 targetSw, destSw);
1242 return false;
1243 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001244 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001245 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001246 // figure out the new next hops at the targetSw towards the destSw
Saurav Das9df5b7c2017-08-14 16:44:43 -07001247 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001248 // call group handler to change hash group at targetSw
1249 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1250 if (grpHandler == null) {
1251 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1252 + " {} hash group buckets for route:{} ", targetSw,
1253 (revoke) ? "revoke" : "repopulate", route);
1254 return false;
1255 }
Saurav Das137f27f2018-06-11 17:02:31 -07001256 log.debug("{} hash-groups buckets For Route {} -> {} to new next-hops {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001257 (revoke) ? "revoke" : "repopulating",
1258 targetSw, destSw, nextHops);
1259 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1260 destSw, true)
1261 : grpHandler.fixHashGroups(targetSw, nextHops,
1262 destSw, false);
1263 }
1264
1265 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001266 * Start the flow rule population process if it was never started. The
1267 * process finishes successfully when all flow rules are set and stops with
1268 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -07001269 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001270 public void startPopulationProcess() {
1271 statusLock.lock();
1272 try {
1273 if (populationStatus == Status.IDLE
1274 || populationStatus == Status.SUCCEEDED
1275 || populationStatus == Status.ABORTED) {
1276 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -07001277 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -07001278 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1279 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001280 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001281 } finally {
1282 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001283 }
sangho20eff1d2015-04-13 15:15:58 -07001284 }
1285
Saurav Dasb5c236e2016-06-07 10:08:06 -07001286 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001287 * Revoke rules of given subnet in all edge switches.
1288 *
1289 * @param subnets subnet being removed
1290 * @return true if succeed
1291 */
1292 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
piere08595d2019-04-24 16:12:47 +02001293 DeviceId targetSw;
1294 List<Future<Boolean>> futures = Lists.newArrayList();
1295 for (Device sw : srManager.deviceService.getAvailableDevices()) {
1296 targetSw = sw.id();
1297 if (shouldProgram(targetSw)) {
1298 futures.add(routePopulators.submit(new RevokeSubnet(targetSw, subnets)));
1299 } else {
1300 futures.add(CompletableFuture.completedFuture(true));
1301 }
1302 }
1303 // check the execution of each job
1304 return checkJobs(futures);
1305 }
1306
Shibu Vijayakumaraa25f602020-01-07 11:45:09 +00001307 /**
1308 * Revoke rules of given subnets in the given switches.
1309 *
1310 * @param targetSwitches switched from which subnets to be removed
1311 * @param subnets subnet bring removed
1312 * @return true if succeed
1313 */
1314 protected boolean revokeSubnet(Set<DeviceId> targetSwitches, Set<IpPrefix> subnets) {
1315 List<Future<Boolean>> futures = Lists.newArrayList();
1316 for (DeviceId targetSw : targetSwitches) {
1317 if (shouldProgram(targetSw)) {
1318 futures.add(routePopulators.submit(new RevokeSubnet(targetSw, subnets)));
1319 } else {
1320 futures.add(CompletableFuture.completedFuture(true));
1321 }
1322 }
1323 // check the execution of each job
1324 return checkJobs(futures);
1325 }
1326
piere08595d2019-04-24 16:12:47 +02001327 private final class RevokeSubnet implements PickyCallable<Boolean> {
1328 private DeviceId targetSw;
1329 private Set<IpPrefix> subnets;
1330
1331 /**
1332 * Builds a RevokeSubnet task, which provides a result.
1333 *
1334 * @param subnets a set of prefixes
1335 * @param targetSw target switch
1336 */
1337 RevokeSubnet(DeviceId targetSw, Set<IpPrefix> subnets) {
1338 this.targetSw = targetSw;
1339 this.subnets = subnets;
1340 }
1341
1342 @Override
1343 public Boolean call() throws Exception {
1344 return srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets);
1345 }
1346
1347 @Override
1348 public int hint() {
1349 return targetSw.hashCode();
Saurav Das7bcbe702017-06-13 15:35:54 -07001350 }
1351 }
1352
1353 /**
Charles Chan2fde6d42017-08-23 14:46:43 -07001354 * Populates IP rules for a route that has direct connection to the switch
1355 * if the current instance is the master of the switch.
1356 *
1357 * @param deviceId device ID of the device that next hop attaches to
1358 * @param prefix IP prefix of the route
1359 * @param hostMac MAC address of the next hop
1360 * @param hostVlanId Vlan ID of the nexthop
1361 * @param outPort port where the next hop attaches to
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001362 * @param directHost host is of type direct or indirect
Charles Chan9797ebb2020-02-14 13:23:57 -08001363 * @return future that includes the flow objective if succeeded, null if otherwise
Charles Chan2fde6d42017-08-23 14:46:43 -07001364 */
Charles Chan9797ebb2020-02-14 13:23:57 -08001365 CompletableFuture<Objective> populateRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac,
1366 VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001367 if (shouldProgram(deviceId)) {
Charles Chan9797ebb2020-02-14 13:23:57 -08001368 return srManager.routingRulePopulator.populateRoute(deviceId, prefix,
1369 hostMac, hostVlanId, outPort, directHost);
Charles Chan2fde6d42017-08-23 14:46:43 -07001370 }
Charles Chan9797ebb2020-02-14 13:23:57 -08001371 return CompletableFuture.completedFuture(null);
Charles Chan2fde6d42017-08-23 14:46:43 -07001372 }
1373
1374 /**
1375 * Removes IP rules for a route when the next hop is gone.
1376 * if the current instance is the master of the switch.
1377 *
1378 * @param deviceId device ID of the device that next hop attaches to
1379 * @param prefix IP prefix of the route
1380 * @param hostMac MAC address of the next hop
1381 * @param hostVlanId Vlan ID of the nexthop
1382 * @param outPort port that next hop attaches to
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001383 * @param directHost host is of type direct or indirect
Charles Chan9797ebb2020-02-14 13:23:57 -08001384 * @return future that carries the flow objective if succeeded, null if otherwise
Charles Chan2fde6d42017-08-23 14:46:43 -07001385 */
Charles Chan9797ebb2020-02-14 13:23:57 -08001386 CompletableFuture<Objective> revokeRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001387 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001388 if (shouldProgram(deviceId)) {
Charles Chan9797ebb2020-02-14 13:23:57 -08001389 return srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId,
1390 outPort, directHost);
Charles Chan2fde6d42017-08-23 14:46:43 -07001391 }
Charles Chan9797ebb2020-02-14 13:23:57 -08001392 return CompletableFuture.completedFuture(null);
Charles Chan2fde6d42017-08-23 14:46:43 -07001393 }
1394
Charles Chan9797ebb2020-02-14 13:23:57 -08001395 CompletableFuture<Objective> populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001396 if (shouldProgram(deviceId)) {
Charles Chan9797ebb2020-02-14 13:23:57 -08001397 return srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001398 }
Charles Chan9797ebb2020-02-14 13:23:57 -08001399 return CompletableFuture.completedFuture(null);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001400 }
1401
Charles Chan9797ebb2020-02-14 13:23:57 -08001402 CompletableFuture<Objective> revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001403 if (shouldProgram(deviceId)) {
Charles Chan9797ebb2020-02-14 13:23:57 -08001404 return srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001405 }
Charles Chan9797ebb2020-02-14 13:23:57 -08001406 return CompletableFuture.completedFuture(null);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001407 }
1408
1409 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1410 VlanId vlanId, boolean popVlan, boolean install) {
1411 if (shouldProgram(deviceId)) {
1412 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1413 }
1414 }
1415
1416 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1417 VlanId vlanId, boolean popVlan, boolean install) {
1418 if (shouldProgram(deviceId)) {
1419 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1420 vlanId, popVlan, install);
1421 }
1422 }
1423
Charles Chan2fde6d42017-08-23 14:46:43 -07001424 /**
Jonghwan Hyun800d9d02018-04-09 09:40:50 -07001425 * Populates IP rules for a route when the next hop is double-tagged.
1426 *
1427 * @param deviceId device ID that next hop attaches to
1428 * @param prefix IP prefix of the route
1429 * @param hostMac MAC address of the next hop
1430 * @param innerVlan Inner Vlan ID of the next hop
1431 * @param outerVlan Outer Vlan ID of the next hop
1432 * @param outerTpid Outer TPID of the next hop
1433 * @param outPort port that the next hop attaches to
1434 */
1435 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1436 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1437 if (srManager.mastershipService.isLocalMaster(deviceId)) {
Charles Chanb7aa7642019-07-26 17:46:15 -07001438 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1439 deviceId, prefix, hostMac, innerVlan, outerVlan, outerTpid, outPort);
1440 srManager.routingRulePopulator.processDoubleTaggedFilter(
1441 deviceId, outPort, outerVlan, innerVlan, true);
Jonghwan Hyun800d9d02018-04-09 09:40:50 -07001442 }
1443 }
1444
1445 /**
1446 * Revokes IP rules for a route when the next hop is double-tagged.
1447 *
1448 * @param deviceId device ID that next hop attaches to
1449 * @param prefix IP prefix of the route
1450 * @param hostMac MAC address of the next hop
1451 * @param innerVlan Inner Vlan ID of the next hop
1452 * @param outerVlan Outer Vlan ID of the next hop
1453 * @param outerTpid Outer TPID of the next hop
1454 * @param outPort port that the next hop attaches to
1455 */
1456 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1457 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1458 // Revoke route either if this node have the mastership (when device is available) or
1459 // if this node is the leader (even when device is unavailable)
1460 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1461 if (srManager.deviceService.isAvailable(deviceId)) {
1462 // Master node will revoke specified rule.
1463 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1464 return;
1465 }
1466
1467 // isLocalMaster will return false when the device is unavailable.
1468 // Verify if this node is the leader in that case.
1469 NodeId leader = srManager.leadershipService.runForLeadership(
1470 deviceId.toString()).leaderNodeId();
1471 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1472 // Leader node will revoke specified rule.
1473 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1474 return;
1475 }
1476 }
1477
Charles Chanb7aa7642019-07-26 17:46:15 -07001478 srManager.routingRulePopulator.revokeDoubleTaggedRoute(deviceId, prefix, hostMac,
1479 innerVlan, outerVlan, outerTpid, outPort);
1480 srManager.routingRulePopulator.processDoubleTaggedFilter(deviceId, outPort, outerVlan, innerVlan, false);
Jonghwan Hyun800d9d02018-04-09 09:40:50 -07001481 }
1482
pier41d389a2020-01-07 15:39:39 +01001483 /**
1484 * Purges seen before routes for a given device.
1485 * @param deviceId the device id
1486 */
1487 void purgeSeenBeforeRoutes(DeviceId deviceId) {
1488 log.debug("Purging seen before routes having as target {}", deviceId);
1489 Set<Entry<DeviceId, DeviceId>> routesToPurge = seenBeforeRoutes.stream()
1490 .filter(entry -> entry.getValue().equals(deviceId))
1491 .collect(Collectors.toSet());
1492 routesToPurge.forEach(entry -> seenBeforeRoutes.remove(entry.getKey(), entry.getValue()));
1493 }
Jonghwan Hyun800d9d02018-04-09 09:40:50 -07001494
1495 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001496 * Remove ECMP graph entry for the given device. Typically called when
1497 * device is no longer available.
1498 *
1499 * @param deviceId the device for which graphs need to be purged
1500 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001501 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Dasc568c342018-01-25 09:49:01 -08001502 statusLock.lock();
1503 try {
Saurav Dasc568c342018-01-25 09:49:01 -08001504 if (populationStatus == Status.STARTED) {
1505 log.warn("Previous rule population is not finished. Cannot"
1506 + " proceeed with purgeEcmpGraph for {}", deviceId);
1507 return;
1508 }
1509 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1510 currentEcmpSpgMap.remove(deviceId);
1511 if (updatedEcmpSpgMap != null) {
1512 updatedEcmpSpgMap.remove(deviceId);
1513 }
1514 } finally {
1515 statusLock.unlock();
Saurav Das7bcbe702017-06-13 15:35:54 -07001516 }
1517 }
1518
Saurav Das201762d2018-04-21 17:19:48 -07001519 /**
1520 * Attempts a full reroute of route-paths if topology has changed relatively
1521 * close to a mastership change event. Does not do a reroute if mastership
1522 * change is due to reasons other than a ONOS cluster event - for example a
1523 * call to balance-masters, or a switch up/down event.
1524 *
1525 * @param devId the device identifier for which mastership has changed
1526 * @param me the mastership event
1527 */
1528 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1529 // give small delay to absorb mastership events that are caused by
1530 // device that has disconnected from cluster
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001531 executorServiceMstChg.schedule(new MasterChange(devId, me),
1532 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das201762d2018-04-21 17:19:48 -07001533 }
1534
1535 protected final class MasterChange implements Runnable {
1536 private DeviceId devId;
1537 private MastershipEvent me;
1538 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1539 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
Saurav Das9a554292018-04-27 18:42:30 -07001540 private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
Saurav Das137f27f2018-06-11 17:02:31 -07001541 private static final long FULL_REROUTE_THRESHOLD = 10000; // ms
Saurav Das201762d2018-04-21 17:19:48 -07001542
1543 MasterChange(DeviceId devId, MastershipEvent me) {
1544 this.devId = devId;
1545 this.me = me;
1546 }
1547
1548 @Override
1549 public void run() {
1550 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1551 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1552
1553 // ignore event for lost switch if cluster event hasn't happened -
1554 // device down event will handle it
1555 if ((me.roleInfo().master() == null
1556 || !srManager.deviceService.isAvailable(devId))
1557 && !clusterEvent) {
1558 log.debug("Full reroute not required for lost device: {}/{} "
1559 + "clusterEvent/timeSince: {}/{}",
1560 devId, me.roleInfo(), clusterEvent, lce);
1561 return;
1562 }
1563
1564 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1565 long lde = Instant.now().toEpochMilli() - update;
1566 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1567
1568 // ignore event for recently connected switch if cluster event hasn't
1569 // happened - link up events will handle it
1570 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1571 && !clusterEvent) {
1572 log.debug("Full reroute not required for recently available"
1573 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1574 + "clusterEvent/timeSince: {}/{}",
1575 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1576 return;
1577 }
1578
Saurav Das9a554292018-04-27 18:42:30 -07001579 long lepe = Instant.now().toEpochMilli()
1580 - srManager.lastEdgePortEvent.toEpochMilli();
1581 boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
1582
Saurav Das201762d2018-04-21 17:19:48 -07001583 // if it gets here, then mastership change is likely due to onos
1584 // instance failure, or network partition in onos cluster
1585 // normally a mastership change like this does not require re-programming
1586 // but if topology changes happen at the same time then we may miss events
1587 if (!isRoutingStable() && clusterEvent) {
Saurav Das9a554292018-04-27 18:42:30 -07001588 log.warn("Mastership changed for dev: {}/{} while programming route-paths "
Saurav Das201762d2018-04-21 17:19:48 -07001589 + "due to clusterEvent {} ms ago .. attempting full reroute",
1590 devId, me.roleInfo(), lce);
1591 if (srManager.mastershipService.isLocalMaster(devId)) {
1592 // old master could have died when populating filters
1593 populatePortAddressingRules(devId);
1594 }
Saurav Das137f27f2018-06-11 17:02:31 -07001595 // old master could have died when creating groups
Saurav Das201762d2018-04-21 17:19:48 -07001596 // XXX right now we have no fine-grained way to only make changes
Saurav Das137f27f2018-06-11 17:02:31 -07001597 // for the route paths affected by this device. Thus we do a
1598 // full reroute after purging all hash groups. We also try to do
1599 // it only once, irrespective of the number of devices
1600 // that changed mastership when their master instance died.
1601 long lfrr = Instant.now().toEpochMilli() - lastFullReroute.toEpochMilli();
1602 boolean doFullReroute = lfrr > FULL_REROUTE_THRESHOLD;
1603 if (doFullReroute) {
1604 lastFullReroute = Instant.now();
1605 for (Device dev : srManager.deviceService.getDevices()) {
1606 if (shouldProgram(dev.id())) {
1607 srManager.purgeHashedNextObjectiveStore(dev.id());
pier41d389a2020-01-07 15:39:39 +01001608 seenBeforeRoutes.removeAll(dev.id());
Saurav Das137f27f2018-06-11 17:02:31 -07001609 }
1610 }
1611 // give small delay to ensure entire store is purged
1612 executorServiceFRR.schedule(new FullRerouteAfterPurge(),
1613 PURGE_DELAY,
1614 TimeUnit.MILLISECONDS);
1615 } else {
1616 log.warn("Full reroute attempted {} ms ago .. skipping", lfrr);
1617 }
Saurav Das9a554292018-04-27 18:42:30 -07001618
1619 } else if (edgePortEvent && clusterEvent) {
1620 log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
1621 + "while edge-port event happened {} ms ago "
1622 + " .. reprogramming all edge-ports",
1623 devId, me.roleInfo(), lce, lepe);
1624 if (shouldProgram(devId)) {
1625 srManager.deviceService.getPorts(devId).stream()
1626 .filter(p -> srManager.interfaceService
1627 .isConfigured(new ConnectPoint(devId, p.number())))
1628 .forEach(p -> srManager.processPortUpdated(devId, p));
1629 }
1630
Saurav Das201762d2018-04-21 17:19:48 -07001631 } else {
1632 log.debug("Stable route-paths .. full reroute not attempted for "
1633 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1634 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1635 deviceEvent, lde, clusterEvent, lce);
1636 }
1637 }
1638 }
1639
Saurav Das137f27f2018-06-11 17:02:31 -07001640 /**
1641 * Performs a full reroute of routing rules in all the switches. Assumes
1642 * caller has purged hash groups from the nextObjective store, otherwise
1643 * re-uses ones available in the store.
1644 */
1645 protected final class FullRerouteAfterPurge implements Runnable {
1646 @Override
1647 public void run() {
1648 populateAllRoutingRules();
1649 }
1650 }
1651
1652
Saurav Das7bcbe702017-06-13 15:35:54 -07001653 //////////////////////////////////////
1654 // Routing helper methods and classes
1655 //////////////////////////////////////
1656
1657 /**
Saurav Das137f27f2018-06-11 17:02:31 -07001658 * Computes set of affected routes due to failed link. Assumes previous ecmp
1659 * shortest-path graph exists for a switch in order to compute affected
1660 * routes. If such a graph does not exist, the method returns null.
Saurav Dasb5c236e2016-06-07 10:08:06 -07001661 *
1662 * @param linkFail the failed link
1663 * @return the set of affected routes which may be empty if no routes were
Saurav Das137f27f2018-06-11 17:02:31 -07001664 * affected
Saurav Dasb5c236e2016-06-07 10:08:06 -07001665 */
sangho20eff1d2015-04-13 15:15:58 -07001666 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001667 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1668
1669 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001670 log.debug("Computing the impacted routes for device {} due to link fail",
1671 sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001672 if (!shouldProgram(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001673 lastProgrammed.remove(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001674 continue;
1675 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001676 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001677 // check for mastership change since last run
1678 if (!lastProgrammed.contains(sw.id())) {
Saurav Das137f27f2018-06-11 17:02:31 -07001679 log.warn("New responsibility for this node to program dev:{}"
Saurav Das201762d2018-04-21 17:19:48 -07001680 + " ... nuking current ECMPspg", sw.id());
1681 currentEcmpSpgMap.remove(sw.id());
1682 }
Saurav Das137f27f2018-06-11 17:02:31 -07001683 lastProgrammed.add(sw.id());
1684
Saurav Das9df5b7c2017-08-14 16:44:43 -07001685 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1686 if (ecmpSpg == null) {
Saurav Das137f27f2018-06-11 17:02:31 -07001687 log.warn("No existing ECMP graph for switch {}. Assuming "
1688 + "all route-paths have changed towards it.", rootSw);
1689 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
1690 if (targetSw.equals(rootSw)) {
1691 continue;
1692 }
1693 routes.add(Lists.newArrayList(targetSw, rootSw));
1694 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1695 }
1696 continue;
Saurav Das9df5b7c2017-08-14 16:44:43 -07001697 }
Saurav Das137f27f2018-06-11 17:02:31 -07001698
Saurav Das9df5b7c2017-08-14 16:44:43 -07001699 if (log.isDebugEnabled()) {
1700 log.debug("Root switch: {}", rootSw);
1701 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1702 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1703 }
1704 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1705 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1706 // figure out if the broken link affected any route-paths in this graph
1707 for (Integer itrIdx : switchVia.keySet()) {
1708 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1709 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1710 switchVia.get(itrIdx);
1711 for (DeviceId targetSw : swViaMap.keySet()) {
1712 log.trace("TargetSwitch {} --> RootSwitch {}",
1713 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001714 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1715 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001716 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001717 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001718 Set<ArrayList<DeviceId>> subLinks =
1719 computeLinks(targetSw, rootSw, swViaMap);
1720 for (ArrayList<DeviceId> alink: subLinks) {
1721 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1722 alink.get(1).equals(linkFail.dst().deviceId()))
1723 ||
1724 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1725 alink.get(1).equals(linkFail.src().deviceId()))) {
1726 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1727 ArrayList<DeviceId> aRoute = new ArrayList<>();
1728 aRoute.add(targetSw); // switch with rules to populate
1729 aRoute.add(rootSw); // towards this destination
1730 routes.add(aRoute);
1731 break;
1732 }
sangho20eff1d2015-04-13 15:15:58 -07001733 }
1734 }
1735 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001736
sangho20eff1d2015-04-13 15:15:58 -07001737 }
sangho45b009c2015-05-07 13:30:57 -07001738
sangho20eff1d2015-04-13 15:15:58 -07001739 }
sangho20eff1d2015-04-13 15:15:58 -07001740 return routes;
1741 }
1742
Saurav Das4e3224f2016-11-29 14:27:25 -08001743 /**
1744 * Computes set of affected routes due to new links or failed switches.
1745 *
Saurav Das604ab3a2018-03-18 21:28:15 -07001746 * @param failedSwitch deviceId of failed switch if any
Saurav Das4e3224f2016-11-29 14:27:25 -08001747 * @return the set of affected routes which may be empty if no routes were
1748 * affected
1749 */
Saurav Dase0d4c872018-03-05 14:37:16 -08001750 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001751 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001752 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001753
1754 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001755 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001756 if (!shouldProgram(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001757 lastProgrammed.remove(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001758 continue;
1759 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001760 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001761 if (log.isTraceEnabled()) {
1762 log.trace("Device links for dev: {}", rootSw);
1763 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1764 log.trace("{} -> {} ", link.src().deviceId(),
1765 link.dst().deviceId());
1766 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001767 }
Saurav Das201762d2018-04-21 17:19:48 -07001768 // check for mastership change since last run
1769 if (!lastProgrammed.contains(sw.id())) {
Saurav Das137f27f2018-06-11 17:02:31 -07001770 log.warn("New responsibility for this node to program dev:{}"
Saurav Das201762d2018-04-21 17:19:48 -07001771 + " ... nuking current ECMPspg", sw.id());
1772 currentEcmpSpgMap.remove(sw.id());
1773 }
Saurav Das137f27f2018-06-11 17:02:31 -07001774 lastProgrammed.add(sw.id());
Saurav Das7bcbe702017-06-13 15:35:54 -07001775 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1776 if (currEcmpSpg == null) {
1777 log.debug("No existing ECMP graph for device {}.. adding self as "
1778 + "changed route", rootSw);
1779 changedRtBldr.add(Lists.newArrayList(rootSw));
1780 continue;
1781 }
1782 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Das5a356042018-04-06 20:16:01 -07001783 if (newEcmpSpg == null) {
1784 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1785 continue;
1786 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001787 if (log.isDebugEnabled()) {
1788 log.debug("Root switch: {}", rootSw);
1789 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1790 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1791 }
1792 // first use the updated/new map to compare to current/existing map
1793 // as new links may have come up
1794 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1795 // then use the current/existing map to compare to updated/new map
1796 // as switch may have been removed
1797 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001798 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001799 }
sangho20eff1d2015-04-13 15:15:58 -07001800
Saurav Dase0d4c872018-03-05 14:37:16 -08001801 // handle clearing state for a failed switch in case the switch does
1802 // not have a pair, or the pair is not available
1803 if (failedSwitch != null) {
Charles Chanba6c5752018-04-02 11:46:38 -07001804 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1805 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dase0d4c872018-03-05 14:37:16 -08001806 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1807 srManager.deviceService.getDevices().forEach(dev -> {
1808 if (!dev.id().equals(failedSwitch) &&
1809 srManager.mastershipService.isLocalMaster(dev.id())) {
1810 log.debug(" : {}", dev.id());
1811 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1812 }
1813 });
1814 }
1815 }
1816
Saurav Das7bcbe702017-06-13 15:35:54 -07001817 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001818 for (ArrayList<DeviceId> route: changedRoutes) {
1819 log.debug("Route changes Target -> Root");
1820 if (route.size() == 1) {
1821 log.debug(" : all -> {}", route.get(0));
1822 } else {
1823 log.debug(" : {} -> {}", route.get(0), route.get(1));
1824 }
1825 }
1826 return changedRoutes;
1827 }
1828
pier52517ff2019-04-25 18:51:51 +02001829 // Utility method to expands the route changes in two elements array using
1830 // the ECMP graph. Caller represents all to dst switch routes with an
1831 // array containing only the dst switch.
1832 private Set<ArrayList<DeviceId>> getExpandedRoutes(Set<ArrayList<DeviceId>> routeChanges) {
1833 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
1834 // Ensure each routeChanges entry has two elements
1835 for (ArrayList<DeviceId> route : routeChanges) {
1836 if (route.size() == 1) {
1837 DeviceId dstSw = route.get(0);
1838 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
1839 if (ec == null) {
1840 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
1841 return Collections.emptySet();
1842 }
1843 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
1844 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
1845 changedRoutes.add(Lists.newArrayList(target, dstSw));
1846 });
1847 });
1848 } else {
1849 DeviceId targetSw = route.get(0);
1850 DeviceId dstSw = route.get(1);
1851 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
1852 }
1853 }
1854 return changedRoutes;
1855 }
1856
1857 // Utility method to expands the route changes in two elements array using
1858 // the available devices. Caller represents all to dst switch routes with an
1859 // array containing only the dst switch.
1860 private Set<ArrayList<DeviceId>> getAllExpandedRoutes(Set<ArrayList<DeviceId>> routeChanges) {
1861 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
1862 // Ensure each routeChanges entry has two elements
1863 for (ArrayList<DeviceId> route : routeChanges) {
1864 if (route.size() == 1) {
1865 // route-path changes are from everyone else to this switch
1866 DeviceId dstSw = route.get(0);
1867 srManager.deviceService.getAvailableDevices().forEach(sw -> {
1868 if (!sw.id().equals(dstSw)) {
1869 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
1870 }
1871 });
1872 } else {
1873 changedRoutes.add(route);
1874 }
1875 }
1876 return changedRoutes;
1877 }
1878
Saurav Das4e3224f2016-11-29 14:27:25 -08001879 /**
1880 * For the root switch, searches all the target nodes reachable in the base
1881 * graph, and compares paths to the ones in the comp graph.
1882 *
1883 * @param base the graph that is indexed for all reachable target nodes
1884 * from the root node
1885 * @param comp the graph that the base graph is compared to
1886 * @param rootSw both ecmp graphs are calculated for the root node
1887 * @return all the routes that have changed in the base graph
1888 */
1889 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1890 EcmpShortestPathGraph comp,
1891 DeviceId rootSw) {
1892 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1893 ImmutableSet.builder();
1894 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1895 base.getAllLearnedSwitchesAndVia();
1896 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1897 comp.getAllLearnedSwitchesAndVia();
1898 for (Integer itrIdx : baseMap.keySet()) {
1899 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1900 baseMap.get(itrIdx);
1901 for (DeviceId targetSw : baseViaMap.keySet()) {
1902 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1903 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1904 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001905 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001906 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001907 route.add(targetSw); // switch with rules to populate
1908 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001909 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001910 }
1911 }
sangho45b009c2015-05-07 13:30:57 -07001912 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001913 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001914 }
1915
Saurav Das7bcbe702017-06-13 15:35:54 -07001916 /**
1917 * Returns the ECMP paths traversed to reach the target switch.
1918 *
1919 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1920 * @param targetSw the switch to reach from the root switch
1921 * @return the nodes traversed on ECMP paths to the target switch
1922 */
sangho20eff1d2015-04-13 15:15:58 -07001923 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001924 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001925 for (Integer itrIdx : switchVia.keySet()) {
1926 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1927 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001928 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001929 continue;
1930 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001931 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001932 }
1933 }
1934
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001935 return null;
sangho20eff1d2015-04-13 15:15:58 -07001936 }
1937
Saurav Das7bcbe702017-06-13 15:35:54 -07001938 /**
1939 * Utility method to break down a path from src to dst device into a collection
1940 * of links.
1941 *
1942 * @param src src device of the path
1943 * @param dst dst device of the path
1944 * @param viaMap path taken from src to dst device
1945 * @return collection of links in the path
1946 */
sangho20eff1d2015-04-13 15:15:58 -07001947 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1948 DeviceId dst,
1949 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1950 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1951 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1952 DeviceId linkSrc = src;
1953 DeviceId linkDst = dst;
1954 for (DeviceId viaDevice: via) {
1955 ArrayList<DeviceId> link = new ArrayList<>();
1956 linkDst = viaDevice;
1957 link.add(linkSrc);
1958 link.add(linkDst);
1959 subLinks.add(link);
1960 linkSrc = viaDevice;
1961 }
1962 ArrayList<DeviceId> link = new ArrayList<>();
1963 link.add(linkSrc);
1964 link.add(dst);
1965 subLinks.add(link);
1966 }
1967
1968 return subLinks;
1969 }
1970
Charles Chan93e71ba2016-04-29 14:38:22 -07001971 /**
Charles Chan2ff1bac2018-03-29 16:03:41 -07001972 * Determines whether this controller instance should program the
Saurav Das7bcbe702017-06-13 15:35:54 -07001973 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chan2ff1bac2018-03-29 16:03:41 -07001974 * <p>
1975 * Once an instance is elected, it will be the only instance responsible for programming
1976 * both devices in the pair until it goes down.
Charles Chan93e71ba2016-04-29 14:38:22 -07001977 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001978 * @param deviceId device identifier to consider for routing
Charles Chan2ff1bac2018-03-29 16:03:41 -07001979 * @return true if current instance should handle the routing for given device
Charles Chan93e71ba2016-04-29 14:38:22 -07001980 */
Charles Chan2ff1bac2018-03-29 16:03:41 -07001981 boolean shouldProgram(DeviceId deviceId) {
Charles Chan50bb6ef2018-04-18 18:41:05 -07001982 Boolean cached = shouldProgramCache.get(deviceId);
1983 if (cached != null) {
Saurav Das201762d2018-04-21 17:19:48 -07001984 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001985 return cached;
1986 }
1987
Charles Chan2ff1bac2018-03-29 16:03:41 -07001988 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001989
Charles Chan2ff1bac2018-03-29 16:03:41 -07001990 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1991 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1992 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
Saurav Das137f27f2018-06-11 17:02:31 -07001993 log.debug("Evaluate shouldProgram {}/pair={}. currentNodeId={}, master={}, pairMaster={}",
Charles Chan2ff1bac2018-03-29 16:03:41 -07001994 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1995
1996 // No pair device configured. Only handle when current instance is the master of the device
1997 if (!pairDeviceId.isPresent()) {
Saurav Das137f27f2018-06-11 17:02:31 -07001998 log.debug("No pair device. currentNodeId={}, master={}", currentNodeId, masterNodeId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001999 return currentNodeId.equals(masterNodeId);
sanghob35a6192015-04-01 13:05:26 -07002000 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07002001
2002 // Should not handle if current instance is not the master of either switch
2003 if (!currentNodeId.equals(masterNodeId) &&
2004 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
Saurav Das137f27f2018-06-11 17:02:31 -07002005 log.debug("Current nodeId {} is neither the master of target device {} nor pair device {}",
Charles Chan2ff1bac2018-03-29 16:03:41 -07002006 currentNodeId, deviceId, pairDeviceId);
2007 return false;
2008 }
2009
2010 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
2011
2012 NodeId king = shouldProgram.compute(key, ((k, v) -> {
2013 if (v == null) {
2014 // There is no value in the map. Elect a node
2015 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
2016 } else {
2017 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
2018 // Use the node in the map if it is still alive and is a master of any of the two switches
2019 return v;
2020 } else {
2021 // Previously elected node is no longer the master of either switch. Re-elect a node.
2022 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
2023 }
2024 }
2025 }));
2026
2027 if (king != null) {
Saurav Das137f27f2018-06-11 17:02:31 -07002028 log.debug("{} is king, should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07002029 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chan2ff1bac2018-03-29 16:03:41 -07002030 return king.equals(currentNodeId);
2031 } else {
2032 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07002033 shouldProgramCache.remove(deviceId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07002034 return false;
2035 }
2036 }
2037
2038 /**
2039 * Elects a node who should take responsibility of programming devices.
2040 * @param nodeIds list of candidate node ID
2041 *
2042 * @return NodeId of the node that gets elected, or null if none of the node can be elected
2043 */
2044 private NodeId elect(List<NodeId> nodeIds) {
2045 // Remove all null elements. This could happen when some device has no master
2046 nodeIds.removeAll(Collections.singleton(null));
2047 nodeIds.sort(null);
2048 return nodeIds.size() == 0 ? null : nodeIds.get(0);
2049 }
2050
Charles Chan50bb6ef2018-04-18 18:41:05 -07002051 void invalidateShouldProgramCache(DeviceId deviceId) {
2052 shouldProgramCache.remove(deviceId);
2053 }
2054
Charles Chan2ff1bac2018-03-29 16:03:41 -07002055 /**
2056 * Returns a set of device ID, containing given device and its pair device if exist.
2057 *
2058 * @param deviceId Device ID
2059 * @return a set of device ID, containing given device and its pair device if exist.
2060 */
2061 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
2062 Set<DeviceId> ret = Sets.newHashSet(deviceId);
2063 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
2064 return ret;
sanghob35a6192015-04-01 13:05:26 -07002065 }
2066
Charles Chan93e71ba2016-04-29 14:38:22 -07002067 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07002068 * Returns the set of deviceIds which are the next hops from the targetSw
2069 * to the dstSw according to the latest ECMP spg.
2070 *
2071 * @param targetSw the switch for which the next-hops are desired
2072 * @param dstSw the switch to which the next-hops lead to from the targetSw
2073 * @return set of next hop deviceIds, could be empty if no next hops are found
2074 */
2075 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
2076 boolean targetIsEdge = false;
2077 try {
2078 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
2079 } catch (DeviceConfigNotFoundException e) {
2080 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
2081 + "continuing to getNextHops", targetSw);
2082 }
2083
2084 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
2085 if (ecmpSpg == null) {
2086 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
2087 return ImmutableSet.of();
2088 }
2089 HashMap<Integer,
2090 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
2091 ecmpSpg.getAllLearnedSwitchesAndVia();
2092 for (Integer itrIdx : switchVia.keySet()) {
2093 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
2094 switchVia.get(itrIdx);
2095 for (DeviceId target : swViaMap.keySet()) {
2096 if (!target.equals(targetSw)) {
2097 continue;
2098 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07002099 // optimization for spines to not use leaves to get
2100 // to a spine or other leaves. Also leaves should not use other
2101 // leaves to get to the destination
2102 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Dasa4020382018-02-14 14:14:54 -08002103 boolean pathdevIsEdge = false;
2104 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Dasc6ff8f02018-04-23 18:42:12 -07002105 log.debug("Evaluating next-hop in path: {}", via);
Saurav Dasa4020382018-02-14 14:14:54 -08002106 for (DeviceId pathdev : via) {
2107 try {
2108 pathdevIsEdge = srManager.deviceConfiguration
2109 .isEdgeDevice(pathdev);
2110 } catch (DeviceConfigNotFoundException e) {
2111 log.warn(e.getMessage());
2112 }
2113 if (pathdevIsEdge) {
Saurav Das137f27f2018-06-11 17:02:31 -07002114 log.debug("Avoiding {} hop path for targetSw:{}"
Saurav Dasa4020382018-02-14 14:14:54 -08002115 + " --> dstSw:{} which goes through an edge"
2116 + " device {} in path {}", itrIdx,
2117 targetSw, dstSw, pathdev, via);
2118 return ImmutableSet.of();
2119 }
2120 }
2121 }
Saurav Das7bcbe702017-06-13 15:35:54 -07002122 }
2123 Set<DeviceId> nextHops = new HashSet<>();
2124 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
2125 if (via.isEmpty()) {
2126 // the dstSw is the next-hop from the targetSw
2127 nextHops.add(dstSw);
2128 } else {
2129 // first elem is next-hop in each ECMP path
2130 nextHops.add(via.get(0));
2131 }
2132 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07002133 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
2134 dstSw, nextHops);
Saurav Das7bcbe702017-06-13 15:35:54 -07002135 return nextHops;
2136 }
2137 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07002138 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -07002139 return ImmutableSet.of(); //no next-hops found
2140 }
2141
Saurav Das7bcbe702017-06-13 15:35:54 -07002142 //////////////////////////////////////
2143 // Filtering rule creation
2144 //////////////////////////////////////
2145
2146 /**
Saurav Das018605f2017-02-18 14:05:44 -08002147 * Populates filtering rules for port, and punting rules
2148 * for gateway IPs, loopback IPs and arp/ndp traffic.
2149 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07002150 *
2151 * @param deviceId Switch ID to set the rules
2152 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07002153 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07002154 // Although device is added, sometimes device store does not have the
2155 // ports for this device yet. It results in missing filtering rules in the
2156 // switch. We will attempt it a few times. If it still does not work,
2157 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08002158 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08002159 if (firstRun == null) {
2160 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07002161 }
Saurav Dasd2fded02016-12-02 15:43:47 -08002162 executorService.schedule(new RetryFilters(deviceId, firstRun),
2163 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07002164 }
2165
2166 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08002167 * RetryFilters populates filtering objectives for a device and keeps retrying
2168 * till the number of ports filtered are constant for a predefined number
2169 * of attempts.
2170 */
2171 protected final class RetryFilters implements Runnable {
2172 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
2173 DeviceId devId;
2174 int counter;
2175 PortFilterInfo prevRun;
2176
2177 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07002178 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08002179 prevRun = previousRun;
2180 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07002181 }
2182
2183 @Override
2184 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07002185 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08002186 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08002187 boolean sameResult = prevRun.equals(thisRun);
2188 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
2189 thisRun, sameResult);
Ray Milkeyc6c9b172018-02-26 09:36:31 -08002190 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08002191 // exponentially increasing intervals for retries
2192 executorService.schedule(this,
2193 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
2194 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08002195 if (!sameResult) {
2196 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
2197 }
Saurav Das59232cf2016-04-27 18:35:50 -07002198 }
Saurav Dasd2fded02016-12-02 15:43:47 -08002199 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07002200 }
Saurav Das59232cf2016-04-27 18:35:50 -07002201 }
piere08595d2019-04-24 16:12:47 +02002202
2203 // Check jobs completion. It returns false if one of the job fails
2204 // and cancel the remaining
2205 private boolean checkJobs(List<Future<Boolean>> futures) {
2206 boolean completed = true;
2207 for (Future<Boolean> future : futures) {
2208 try {
2209 if (completed) {
2210 if (!future.get()) {
2211 completed = false;
2212 }
2213 } else {
2214 future.cancel(true);
2215 }
2216 } catch (InterruptedException | ExecutionException e) {
2217 completed = false;
2218 }
2219 }
2220 return completed;
2221 }
sanghob35a6192015-04-01 13:05:26 -07002222}