blob: ff0f2a6b1a5ca96859cb6b99bcc72447ba14fa7e [file] [log] [blame]
sanghob35a6192015-04-01 13:05:26 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sanghob35a6192015-04-01 13:05:26 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.segmentrouting;
17
Saurav Dasd2fded02016-12-02 15:43:47 -080018import com.google.common.base.MoreObjects;
Saurav Dasc88d4662017-05-15 15:34:25 -070019import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableMap.Builder;
Charles Chan93e71ba2016-04-29 14:38:22 -070021import com.google.common.collect.ImmutableSet;
Saurav Das4e3224f2016-11-29 14:27:25 -080022import com.google.common.collect.Lists;
sangho20eff1d2015-04-13 15:15:58 -070023import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
Saurav Dasceccf242017-08-03 18:30:35 -070025
sangho666cd6d2015-04-14 16:27:13 -070026import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080027import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070028import org.onlab.packet.IpPrefix;
Charles Chan2fde6d42017-08-23 14:46:43 -070029import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
Saurav Das7bcbe702017-06-13 15:35:54 -070031import org.onosproject.cluster.NodeId;
Charles Chan93e71ba2016-04-29 14:38:22 -070032import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070033import org.onosproject.net.Device;
34import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070035import org.onosproject.net.Link;
Charles Chan2fde6d42017-08-23 14:46:43 -070036import org.onosproject.net.PortNumber;
Charles Chan0b4e6182015-11-03 10:42:14 -080037import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
38import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070039import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
sanghob35a6192015-04-01 13:05:26 -070040import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070043import java.time.Instant;
sanghob35a6192015-04-01 13:05:26 -070044import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.HashSet;
Saurav Das7bcbe702017-06-13 15:35:54 -070047import java.util.Iterator;
48import java.util.Map;
Saurav Dasd2fded02016-12-02 15:43:47 -080049import java.util.Objects;
sanghob35a6192015-04-01 13:05:26 -070050import java.util.Set;
Saurav Das59232cf2016-04-27 18:35:50 -070051import java.util.concurrent.ScheduledExecutorService;
52import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090053import java.util.concurrent.locks.Lock;
54import java.util.concurrent.locks.ReentrantLock;
Saurav Dasd2fded02016-12-02 15:43:47 -080055import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventree0ae7a32016-11-23 09:57:42 -080056import static com.google.common.base.Preconditions.checkNotNull;
57import static java.util.concurrent.Executors.newScheduledThreadPool;
58import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070059
Charles Chane849c192016-01-11 18:28:54 -080060/**
61 * Default routing handler that is responsible for route computing and
62 * routing rule population.
63 */
sanghob35a6192015-04-01 13:05:26 -070064public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080065 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey3717e602018-02-01 13:49:47 -080066 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Das018605f2017-02-18 14:05:44 -080067 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070068 private static final long STABLITY_THRESHOLD = 10; //secs
Charles Chan93e71ba2016-04-29 14:38:22 -070069 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070070
71 private SegmentRoutingManager srManager;
72 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053073 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
74 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070075 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090076 private final Lock statusLock = new ReentrantLock();
77 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070078 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080079 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070080 private Instant lastRoutingChange;
sanghob35a6192015-04-01 13:05:26 -070081
82 /**
83 * Represents the default routing population status.
84 */
85 public enum Status {
86 // population process is not started yet.
87 IDLE,
88
89 // population process started.
90 STARTED,
91
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -070092 // population process was aborted due to errors, mostly for groups not
93 // found.
sanghob35a6192015-04-01 13:05:26 -070094 ABORTED,
95
96 // population process was finished successfully.
97 SUCCEEDED
98 }
99
100 /**
101 * Creates a DefaultRoutingHandler object.
102 *
103 * @param srManager SegmentRoutingManager object
104 */
105 public DefaultRoutingHandler(SegmentRoutingManager srManager) {
106 this.srManager = srManager;
107 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700108 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700109 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700110 this.currentEcmpSpgMap = Maps.newHashMap();
sanghob35a6192015-04-01 13:05:26 -0700111 }
112
113 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700114 * Returns an immutable copy of the current ECMP shortest-path graph as
115 * computed by this controller instance.
116 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700117 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700118 */
119 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
120 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
121 currentEcmpSpgMap.entrySet().forEach(entry -> {
122 if (entry.getValue() != null) {
123 builder.put(entry.getKey(), entry.getValue());
124 }
125 });
126 return builder.build();
127 }
128
Saurav Dasceccf242017-08-03 18:30:35 -0700129 /**
130 * Acquires the lock used when making routing changes.
131 */
132 public void acquireRoutingLock() {
133 statusLock.lock();
134 }
135
136 /**
137 * Releases the lock used when making routing changes.
138 */
139 public void releaseRoutingLock() {
140 statusLock.unlock();
141 }
142
143 /**
144 * Determines if routing in the network has been stable in the last
145 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
146 * routing change timestamp.
147 *
148 * @return true if stable
149 */
150 public boolean isRoutingStable() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700151 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
152 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700153 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700154 return (now - last) > STABLITY_THRESHOLD;
155 }
156
157
Saurav Das7bcbe702017-06-13 15:35:54 -0700158 //////////////////////////////////////
159 // Route path handling
160 //////////////////////////////////////
161
Saurav Das45f48152018-01-18 12:07:33 -0800162 /* The following three methods represent the three major ways in which
163 * route-path handling is triggered in the network
Saurav Das7bcbe702017-06-13 15:35:54 -0700164 * a) due to configuration change
165 * b) due to route-added event
166 * c) due to change in the topology
167 */
168
Saurav Dasc88d4662017-05-15 15:34:25 -0700169 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700170 * Populates all routing rules to all switches. Typically triggered at
171 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700172 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700173 public void populateAllRoutingRules() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700174 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900175 statusLock.lock();
176 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700177 if (populationStatus == Status.STARTED) {
178 log.warn("Previous rule population is not finished. Cannot"
179 + " proceed with populateAllRoutingRules");
180 return;
181 }
182
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900183 populationStatus = Status.STARTED;
184 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700185 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900186 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700187
Saurav Das7bcbe702017-06-13 15:35:54 -0700188 // take a snapshot of the topology
189 updatedEcmpSpgMap = new HashMap<>();
190 Set<EdgePair> edgePairs = new HashSet<>();
191 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800192 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700193 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800194 new EcmpShortestPathGraph(dstSw, srManager);
195 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
196 DeviceId pairDev = getPairDev(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700197 if (pairDev != null) {
198 // pairDev may not be available yet, but we still need to add
199 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
200 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800201 edgePairs.add(new EdgePair(dstSw, pairDev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700202 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800203 DeviceId ret = shouldHandleRouting(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700204 if (ret == null) {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900205 continue;
206 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800207 Set<DeviceId> devsToProcess = Sets.newHashSet(dstSw, ret);
Saurav Das7bcbe702017-06-13 15:35:54 -0700208 // To do a full reroute, assume all routes have changed
209 for (DeviceId dev : devsToProcess) {
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800210 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
211 if (targetSw.equals(dev)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700212 continue;
213 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800214 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700215 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900216 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700217 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900218
Saurav Das7bcbe702017-06-13 15:35:54 -0700219 if (!redoRouting(routeChanges, edgePairs, null)) {
220 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
221 populationStatus = Status.ABORTED;
222 log.warn("Failed to repopulate all routing rules.");
223 return;
sanghob35a6192015-04-01 13:05:26 -0700224 }
225
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900226 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
227 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700228 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900229 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700230 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900231 } finally {
232 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700233 }
sanghob35a6192015-04-01 13:05:26 -0700234 }
235
sangho20eff1d2015-04-13 15:15:58 -0700236 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700237 * Populate rules from all other edge devices to the connect-point(s)
238 * specified for the given subnets.
239 *
240 * @param cpts connect point(s) of the subnets being added
241 * @param subnets subnets being added
Charles Chan2fde6d42017-08-23 14:46:43 -0700242 */
243 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700244 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan71e64f12017-09-11 15:21:57 -0700245 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
246 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
247 return;
248 }
249
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700250 lastRoutingChange = Instant.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700251 statusLock.lock();
252 try {
253 if (populationStatus == Status.STARTED) {
254 log.warn("Previous rule population is not finished. Cannot"
255 + " proceed with routing rules for added routes");
256 return;
257 }
258 populationStatus = Status.STARTED;
259 rulePopulator.resetCounter();
Charles Chan2fde6d42017-08-23 14:46:43 -0700260 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
261 subnets, cpts);
Saurav Dasc568c342018-01-25 09:49:01 -0800262 // In principle an update to a subnet/prefix should not require a
263 // new ECMPspg calculation as it is not a topology event. As a
264 // result, we use the current/existing ECMPspg in the updated map
265 // used by the redoRouting method.
Saurav Das15a81782018-02-09 09:15:03 -0800266 if (updatedEcmpSpgMap == null) {
267 updatedEcmpSpgMap = new HashMap<>();
268 }
Saurav Dasc568c342018-01-25 09:49:01 -0800269 currentEcmpSpgMap.entrySet().forEach(entry -> {
270 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase7f51012018-02-09 17:26:45 -0800271 if (log.isTraceEnabled()) {
272 log.trace("Root switch: {}", entry.getKey());
273 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Dasc568c342018-01-25 09:49:01 -0800274 }
275 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700276 Set<EdgePair> edgePairs = new HashSet<>();
277 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
278 boolean handleRouting = false;
279
280 if (cpts.size() == 2) {
281 // ensure connect points are edge-pairs
282 Iterator<ConnectPoint> iter = cpts.iterator();
283 DeviceId dev1 = iter.next().deviceId();
284 DeviceId pairDev = getPairDev(dev1);
285 if (iter.next().deviceId().equals(pairDev)) {
286 edgePairs.add(new EdgePair(dev1, pairDev));
287 } else {
288 log.warn("Connectpoints {} for subnets {} not on "
289 + "pair-devices.. aborting populateSubnet", cpts, subnets);
290 populationStatus = Status.ABORTED;
291 return;
292 }
293 for (ConnectPoint cp : cpts) {
Saurav Dasc568c342018-01-25 09:49:01 -0800294 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
295 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700296 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800297 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
298 log.warn("populateSubnet: no updated graph for dev:{}"
299 + " ... creating", cp.deviceId());
300 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700301 DeviceId retId = shouldHandleRouting(cp.deviceId());
302 if (retId == null) {
303 continue;
304 }
305 handleRouting = true;
306 }
307 } else {
308 // single connect point
309 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Dasc568c342018-01-25 09:49:01 -0800310 if (updatedEcmpSpgMap.get(dstSw) == null) {
311 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700312 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800313 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
314 log.warn("populateSubnet: no updated graph for dev:{}"
315 + " ... creating", dstSw);
316 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700317 if (srManager.mastershipService.isLocalMaster(dstSw)) {
318 handleRouting = true;
319 }
320 }
321
322 if (!handleRouting) {
323 log.debug("This instance is not handling ecmp routing to the "
324 + "connectPoint(s) {}", cpts);
325 populationStatus = Status.ABORTED;
326 return;
327 }
328
329 // if it gets here, this instance should handle routing for the
330 // connectpoint(s). Assume all route-paths have to be updated to
331 // the connectpoint(s) with the following exceptions
332 // 1. if target is non-edge no need for routing rules
333 // 2. if target is one of the connectpoints
334 for (ConnectPoint cp : cpts) {
335 DeviceId dstSw = cp.deviceId();
336 for (Device targetSw : srManager.deviceService.getDevices()) {
337 boolean isEdge = false;
338 try {
339 isEdge = config.isEdgeDevice(targetSw.id());
340 } catch (DeviceConfigNotFoundException e) {
Charles Chan92726132018-02-16 17:20:54 -0800341 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
342 continue;
Saurav Das7bcbe702017-06-13 15:35:54 -0700343 }
344 if (dstSw.equals(targetSw.id()) || !isEdge ||
345 (cpts.size() == 2 &&
346 targetSw.id().equals(getPairDev(dstSw)))) {
347 continue;
348 }
349 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
350 }
351 }
352
353 if (!redoRouting(routeChanges, edgePairs, subnets)) {
354 log.debug("populateSubnet: populationStatus is ABORTED");
355 populationStatus = Status.ABORTED;
356 log.warn("Failed to repopulate the rules for subnet.");
357 return;
358 }
359
360 log.debug("populateSubnet: populationStatus is SUCCEEDED");
361 populationStatus = Status.SUCCEEDED;
362 log.info("Completed subnet population. Total # of rules pushed : {}",
363 rulePopulator.getCounter());
364 return;
365
366 } finally {
367 statusLock.unlock();
368 }
369 }
370
371 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700372 * Populates the routing rules or makes hash group changes according to the
373 * route-path changes due to link failure, switch failure or link up. This
374 * method should only be called for one of these three possible event-types.
375 * Note that when a switch goes away, all of its links fail as well,
376 * but this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700377 *
Saurav Dasc88d4662017-05-15 15:34:25 -0700378 * @param linkDown the single failed link, or null for other conditions
379 * such as link-up or a removed switch
380 * @param linkUp the single link up, or null for other conditions such as
381 * link-down or a removed switch
382 * @param switchDown the removed switch, or null for other conditions such as
383 * link-down or link-up
Saurav Das7bcbe702017-06-13 15:35:54 -0700384 */ // refactor
Saurav Dasc88d4662017-05-15 15:34:25 -0700385 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
386 Link linkUp,
387 DeviceId switchDown) {
388 if ((linkDown != null && (linkUp != null || switchDown != null)) ||
389 (linkUp != null && (linkDown != null || switchDown != null)) ||
390 (switchDown != null && (linkUp != null || linkDown != null))) {
391 log.warn("Only one event can be handled for link status change .. aborting");
392 return;
393 }
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700394 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900395 statusLock.lock();
396 try {
sangho20eff1d2015-04-13 15:15:58 -0700397
398 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700399 log.warn("Previous rule population is not finished. Cannot"
Saurav Dasc568c342018-01-25 09:49:01 -0800400 + " proceeed with routingRules for Topology change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700401 return;
sangho20eff1d2015-04-13 15:15:58 -0700402 }
403
Saurav Das7bcbe702017-06-13 15:35:54 -0700404 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700405 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700406 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700407 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530408 EcmpShortestPathGraph ecmpSpgUpdated =
409 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700410 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Saurav Das7bcbe702017-06-13 15:35:54 -0700411 DeviceId pairDev = getPairDev(sw.id());
412 if (pairDev != null) {
413 // pairDev may not be available yet, but we still need to add
414 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
415 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
416 edgePairs.add(new EdgePair(sw.id(), pairDev));
417 }
sangho45b009c2015-05-07 13:30:57 -0700418 }
419
Saurav Dasc568c342018-01-25 09:49:01 -0800420 log.info("Starting to populate routing rules from Topology change");
sangho52abe3a2015-05-05 14:13:34 -0700421
sangho20eff1d2015-04-13 15:15:58 -0700422 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700423 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700424 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700425 populationStatus = Status.STARTED;
Saurav Dasc568c342018-01-25 09:49:01 -0800426 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
427 boolean hashGroupsChanged = false;
Saurav Das4e3224f2016-11-29 14:27:25 -0800428 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700429 if (linkDown == null) {
430 // either a linkUp or a switchDown - compute all route changes by
431 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dase0d4c872018-03-05 14:37:16 -0800432 routeChanges = computeRouteChange(switchDown);
Saurav Dasc88d4662017-05-15 15:34:25 -0700433
Saurav Das9df5b7c2017-08-14 16:44:43 -0700434 // deal with linkUp of a seen-before link
Saurav Das45f48152018-01-18 12:07:33 -0800435 if (linkUp != null && srManager.linkHandler.isSeenLink(linkUp)) {
436 if (!srManager.linkHandler.isBidirectional(linkUp)) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700437 log.warn("Not a bidirectional link yet .. not "
438 + "processing link {}", linkUp);
Saurav Das45f48152018-01-18 12:07:33 -0800439 srManager.linkHandler.updateSeenLink(linkUp, true);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700440 populationStatus = Status.ABORTED;
441 return;
Saurav Dasc88d4662017-05-15 15:34:25 -0700442 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700443 // link previously seen before
444 // do hash-bucket changes instead of a re-route
445 processHashGroupChange(routeChanges, false, null);
446 // clear out routesChanges so a re-route is not attempted
447 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800448 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700449 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700450 // for a linkUp of a never-seen-before link
451 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700452
453 // now that we are past the check for a previously seen link
454 // it is safe to update the store for the linkUp
455 if (linkUp != null) {
Saurav Das45f48152018-01-18 12:07:33 -0800456 srManager.linkHandler.updateSeenLink(linkUp, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700457 }
458
Saurav Das9df5b7c2017-08-14 16:44:43 -0700459 //deal with switchDown
460 if (switchDown != null) {
461 processHashGroupChange(routeChanges, true, switchDown);
462 // clear out routesChanges so a re-route is not attempted
463 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800464 hashGroupsChanged = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700465 }
sangho20eff1d2015-04-13 15:15:58 -0700466 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700467 // link has gone down
468 // Compare existing ECMP SPG only with the link that went down
469 routeChanges = computeDamagedRoutes(linkDown);
470 if (routeChanges != null) {
471 processHashGroupChange(routeChanges, true, null);
472 // clear out routesChanges so a re-route is not attempted
473 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800474 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700475 }
sangho20eff1d2015-04-13 15:15:58 -0700476 }
477
Saurav Das4e3224f2016-11-29 14:27:25 -0800478 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700479 if (routeChanges == null) {
Saurav Dasc568c342018-01-25 09:49:01 -0800480 log.warn("Optimized routing failed... opting for full reroute");
Saurav Das7bcbe702017-06-13 15:35:54 -0700481 populationStatus = Status.ABORTED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700482 populateAllRoutingRules();
483 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700484 }
485
sangho20eff1d2015-04-13 15:15:58 -0700486 if (routeChanges.isEmpty()) {
Saurav Dasc568c342018-01-25 09:49:01 -0800487 if (hashGroupsChanged) {
488 log.info("Hash-groups changed for link status change");
489 } else {
490 log.info("No re-route or re-hash attempted for the link"
491 + " status change");
492 updatedEcmpSpgMap.keySet().forEach(devId -> {
493 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
494 log.debug("Updating ECMPspg for remaining dev:{}", devId);
495 });
496 }
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700497 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700498 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700499 return;
sangho20eff1d2015-04-13 15:15:58 -0700500 }
501
Saurav Dasc88d4662017-05-15 15:34:25 -0700502 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700503 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700504 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700505 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700506 log.info("Completed repopulation of rules for link-status change."
507 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700508 return;
sangho20eff1d2015-04-13 15:15:58 -0700509 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700510 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700511 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700512 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700513 return;
sangho20eff1d2015-04-13 15:15:58 -0700514 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900515 } finally {
516 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700517 }
518 }
519
Saurav Dasc88d4662017-05-15 15:34:25 -0700520 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700521 * Processes a set a route-path changes by reprogramming routing rules and
522 * creating new hash-groups or editing them if necessary. This method also
523 * determines the next-hops for the route-path from the src-switch (target)
524 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700525 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700526 * @param routeChanges a set of route-path changes, where each route-path is
527 * a list with its first element the src-switch (target)
528 * of the path, and the second element the dst-switch of
529 * the path.
530 * @param edgePairs a set of edge-switches that are paired by configuration
531 * @param subnets a set of prefixes that need to be populated in the routing
532 * table of the target switch in the route-path. Can be null,
533 * in which case all the prefixes belonging to the dst-switch
534 * will be populated in the target switch
535 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700536 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700537 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
538 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
539 // first make every entry two-elements
540 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
541 for (ArrayList<DeviceId> route : routeChanges) {
542 if (route.size() == 1) {
543 DeviceId dstSw = route.get(0);
544 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
545 if (ec == null) {
546 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
547 return false;
548 }
549 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
550 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
551 changedRoutes.add(Lists.newArrayList(target, dstSw));
552 });
553 });
554 } else {
555 DeviceId targetSw = route.get(0);
556 DeviceId dstSw = route.get(1);
557 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
558 }
559 }
560
561 // now process changedRoutes according to edgePairs
562 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
563 return false; //abort routing and fail fast
564 }
565
566 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Dasc568c342018-01-25 09:49:01 -0800567 Set<DeviceId> updatedDevices = Sets.newHashSet();
568 if (!redoRoutingIndividualDests(subnets, changedRoutes,
569 updatedDevices)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700570 return false; //abort routing and fail fast
571 }
572
Saurav Das7bcbe702017-06-13 15:35:54 -0700573 // update ecmpSPG for all edge-pairs
574 for (EdgePair ep : edgePairs) {
575 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
576 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
577 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
578 }
Saurav Dasc568c342018-01-25 09:49:01 -0800579
580 // here is where we update all devices not touched by this instance
581 updatedEcmpSpgMap.keySet().stream()
582 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
583 .filter(devId -> !updatedDevices.contains(devId))
584 .forEach(devId -> {
585 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
586 log.debug("Updating ECMPspg for remaining dev:{}", devId);
587 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700588 return true;
589 }
590
591 /**
592 * Programs targetSw in the changedRoutes for given prefixes reachable by
593 * an edgePair. If no prefixes are given, the method will use configured
594 * subnets/prefixes. If some configured subnets belong only to a specific
595 * destination in the edgePair, then the target switch will be programmed
596 * only to that destination.
597 *
598 * @param edgePairs set of edge-pairs for which target will be programmed
599 * @param subnets a set of prefixes that need to be populated in the routing
600 * table of the target switch in the changedRoutes. Can be null,
601 * in which case all the configured prefixes belonging to the
602 * paired switches will be populated in the target switch
603 * @param changedRoutes a set of route-path changes, where each route-path is
604 * a list with its first element the src-switch (target)
605 * of the path, and the second element the dst-switch of
606 * the path.
607 * @return true if successful
608 */
609 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
610 Set<IpPrefix> subnets,
611 Set<ArrayList<DeviceId>> changedRoutes) {
612 for (EdgePair ep : edgePairs) {
613 // temp store for a target's changedRoutes to this edge-pair
614 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
615 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
616 while (i.hasNext()) {
617 ArrayList<DeviceId> route = i.next();
618 DeviceId dstSw = route.get(1);
619 if (ep.includes(dstSw)) {
620 // routeChange for edge pair found
621 // sort by target iff target is edge and remove from changedRoutes
622 DeviceId targetSw = route.get(0);
623 try {
624 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
625 continue;
626 }
627 } catch (DeviceConfigNotFoundException e) {
628 log.warn(e.getMessage() + "aborting redoRouting");
629 return false;
630 }
631 // route is from another edge to this edge-pair
632 if (targetRoutes.containsKey(targetSw)) {
633 targetRoutes.get(targetSw).add(route);
634 } else {
635 Set<ArrayList<DeviceId>> temp = new HashSet<>();
636 temp.add(route);
637 targetRoutes.put(targetSw, temp);
638 }
639 i.remove();
640 }
641 }
642 // so now for this edgepair we have a per target set of routechanges
643 // process target->edgePair route
644 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
645 targetRoutes.entrySet()) {
646 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
647 entry.getKey(), ep);
648 DeviceId targetSw = entry.getKey();
649 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
650 entry.getValue().forEach(route -> {
651 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
652 log.debug("route: target {} -> dst {} found with next-hops {}",
653 route.get(0), route.get(1), nhops);
654 perDstNextHops.put(route.get(1), nhops);
655 });
656 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
657 : subnets;
658 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
659 : subnets;
660 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
661 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Dasc568c342018-01-25 09:49:01 -0800662 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
663 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700664 // handle routing to subnets common to edge-pair
Saurav Dasc568c342018-01-25 09:49:01 -0800665 // only if the targetSw is not part of the edge-pair and there
666 // exists a next hop to at least one of the devices in the edge-pair
667 if (!ep.includes(targetSw)
668 && ((nhDev1 != null && !nhDev1.isEmpty())
669 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700670 if (!populateEcmpRoutingRulePartial(
671 targetSw,
672 ep.dev1, ep.dev2,
673 perDstNextHops,
674 Sets.intersection(ipDev1, ipDev2))) {
675 return false; // abort everything and fail fast
676 }
677 }
Saurav Dasc568c342018-01-25 09:49:01 -0800678 // handle routing to subnets that only belong to dev1 only if
679 // a next-hop exists from the target to dev1
Saurav Das7bcbe702017-06-13 15:35:54 -0700680 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Dasc568c342018-01-25 09:49:01 -0800681 if (!onlyDev1Subnets.isEmpty()
682 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700683 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800684 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das7bcbe702017-06-13 15:35:54 -0700685 if (!populateEcmpRoutingRulePartial(
686 targetSw,
687 ep.dev1, null,
688 onlyDev1NextHops,
689 onlyDev1Subnets)) {
690 return false; // abort everything and fail fast
691 }
692 }
Saurav Dasc568c342018-01-25 09:49:01 -0800693 // handle routing to subnets that only belong to dev2 only if
694 // a next-hop exists from the target to dev2
Saurav Das7bcbe702017-06-13 15:35:54 -0700695 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Dasc568c342018-01-25 09:49:01 -0800696 if (!onlyDev2Subnets.isEmpty()
697 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700698 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800699 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700700 if (!populateEcmpRoutingRulePartial(
701 targetSw,
702 ep.dev2, null,
703 onlyDev2NextHops,
704 onlyDev2Subnets)) {
705 return false; // abort everything and fail fast
706 }
707 }
708 }
709 // if it gets here it has succeeded for all targets to this edge-pair
710 }
711 return true;
712 }
713
714 /**
715 * Programs targetSw in the changedRoutes for given prefixes reachable by
716 * a destination switch that is not part of an edge-pair.
717 * If no prefixes are given, the method will use configured subnets/prefixes.
718 *
719 * @param subnets a set of prefixes that need to be populated in the routing
720 * table of the target switch in the changedRoutes. Can be null,
721 * in which case all the configured prefixes belonging to the
722 * paired switches will be populated in the target switch
723 * @param changedRoutes a set of route-path changes, where each route-path is
724 * a list with its first element the src-switch (target)
725 * of the path, and the second element the dst-switch of
726 * the path.
727 * @return true if successful
728 */
729 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Dasc568c342018-01-25 09:49:01 -0800730 Set<ArrayList<DeviceId>> changedRoutes,
731 Set<DeviceId> updatedDevices) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700732 // aggregate route-path changes for each dst device
733 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
734 new HashMap<>();
735 for (ArrayList<DeviceId> route: changedRoutes) {
736 DeviceId dstSw = route.get(1);
737 ArrayList<ArrayList<DeviceId>> deviceRoutes =
738 routesBydevice.get(dstSw);
739 if (deviceRoutes == null) {
740 deviceRoutes = new ArrayList<>();
741 routesBydevice.put(dstSw, deviceRoutes);
742 }
743 deviceRoutes.add(route);
744 }
745 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
746 ArrayList<ArrayList<DeviceId>> deviceRoutes =
747 routesBydevice.get(impactedDstDevice);
748 for (ArrayList<DeviceId> route: deviceRoutes) {
749 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
750 route.get(0), route.get(1));
751 DeviceId targetSw = route.get(0);
752 DeviceId dstSw = route.get(1); // same as impactedDstDevice
753 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Dasbd071d82018-01-09 17:38:44 -0800754 if (nextHops.isEmpty()) {
755 log.warn("Could not find next hop from target:{} --> dst {} "
756 + "skipping this route", targetSw, dstSw);
757 continue;
758 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700759 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
760 nhops.put(dstSw, nextHops);
761 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
762 (subnets == null) ? Sets.newHashSet() : subnets)) {
763 return false; // abort routing and fail fast
764 }
765 log.debug("Populating flow rules from target: {} to dst: {}"
766 + " is successful", targetSw, dstSw);
767 }
768 //Only if all the flows for all impacted routes to a
769 //specific target are pushed successfully, update the
770 //ECMP graph for that target. Or else the next event
771 //would not see any changes in the ECMP graphs.
772 //In another case, the target switch has gone away, so
773 //routes can't be installed. In that case, the current map
774 //is updated here, without any flows being pushed.
775 currentEcmpSpgMap.put(impactedDstDevice,
776 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Dasc568c342018-01-25 09:49:01 -0800777 updatedDevices.add(impactedDstDevice);
Saurav Das7bcbe702017-06-13 15:35:54 -0700778 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
779 }
780 return true;
781 }
782
783 /**
784 * Populate ECMP rules for subnets from target to destination via nexthops.
785 *
786 * @param targetSw Device ID of target switch in which rules will be programmed
787 * @param destSw1 Device ID of final destination switch to which the rules will forward
788 * @param destSw2 Device ID of paired destination switch to which the rules will forward
789 * A null deviceId indicates packets should only be sent to destSw1
Saurav Dasa4020382018-02-14 14:14:54 -0800790 * @param nextHops Map of a set of next hops per destSw
Saurav Das7bcbe702017-06-13 15:35:54 -0700791 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
792 * @return true if it succeeds in populating rules
793 */ // refactor
794 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
795 DeviceId destSw1,
796 DeviceId destSw2,
797 Map<DeviceId, Set<DeviceId>> nextHops,
798 Set<IpPrefix> subnets) {
799 boolean result;
800 // If both target switch and dest switch are edge routers, then set IP
801 // rule for both subnet and router IP.
802 boolean targetIsEdge;
803 boolean dest1IsEdge;
804 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
805 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
806
807 try {
808 targetIsEdge = config.isEdgeDevice(targetSw);
809 dest1IsEdge = config.isEdgeDevice(destSw1);
810 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
811 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
812 if (destSw2 != null) {
813 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
814 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
815 }
816 } catch (DeviceConfigNotFoundException e) {
817 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700818 return false;
819 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700820
821 if (targetIsEdge && dest1IsEdge) {
822 subnets = (subnets != null && !subnets.isEmpty())
823 ? Sets.newHashSet(subnets)
824 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Dasa4020382018-02-14 14:14:54 -0800825 // XXX - Rethink this - ignoring routerIPs in all other switches
826 // even edge to edge switches
Saurav Das7bcbe702017-06-13 15:35:54 -0700827 /*subnets.add(dest1RouterIpv4.toIpPrefix());
828 if (dest1RouterIpv6 != null) {
829 subnets.add(dest1RouterIpv6.toIpPrefix());
830 }
831 if (destSw2 != null && dest2RouterIpv4 != null) {
832 subnets.add(dest2RouterIpv4.toIpPrefix());
833 if (dest2RouterIpv6 != null) {
834 subnets.add(dest2RouterIpv6.toIpPrefix());
835 }
836 }*/
837 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
838 + "for subnets {}", targetSw, destSw1,
839 (destSw2 != null) ? ("& " + destSw2) : "",
840 subnets);
841 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
842 destSw1, destSw2,
843 nextHops);
844 if (!result) {
845 return false;
846 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700847 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700848
849 if (!targetIsEdge && dest1IsEdge) {
850 // MPLS rules in all non-edge target devices. These rules are for
851 // individual destinations, even if the dsts are part of edge-pairs.
852 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
853 + "all MPLS rules", targetSw, destSw1);
854 result = rulePopulator.populateMplsRule(targetSw, destSw1,
855 nextHops.get(destSw1),
856 dest1RouterIpv4);
857 if (!result) {
858 return false;
859 }
860 if (dest1RouterIpv6 != null) {
Saurav Dasa4020382018-02-14 14:14:54 -0800861 int v4sid = 0, v6sid = 0;
862 try {
863 v4sid = config.getIPv4SegmentId(destSw1);
864 v6sid = config.getIPv6SegmentId(destSw1);
865 } catch (DeviceConfigNotFoundException e) {
866 log.warn(e.getMessage());
867 }
868 if (v4sid != v6sid) {
869 result = rulePopulator.populateMplsRule(targetSw, destSw1,
870 nextHops.get(destSw1),
871 dest1RouterIpv6);
872 if (!result) {
873 return false;
874 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700875 }
876 }
877 }
878
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -0700879 if (!targetIsEdge && !dest1IsEdge) {
880 // MPLS rules for inter-connected spines
881 // can be merged with above if, left it here for clarity
882 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
883 + "all MPLS rules", targetSw, destSw1);
884
885 result = rulePopulator.populateMplsRule(targetSw, destSw1,
886 nextHops.get(destSw1),
887 dest1RouterIpv4);
888 if (!result) {
889 return false;
890 }
891
892 if (dest1RouterIpv6 != null) {
893 int v4sid = 0, v6sid = 0;
894 try {
895 v4sid = config.getIPv4SegmentId(destSw1);
896 v6sid = config.getIPv6SegmentId(destSw1);
897 } catch (DeviceConfigNotFoundException e) {
898 log.warn(e.getMessage());
899 }
900 if (v4sid != v6sid) {
901 result = rulePopulator.populateMplsRule(targetSw, destSw1,
902 nextHops.get(destSw1),
903 dest1RouterIpv6);
904 if (!result) {
905 return false;
906 }
907 }
908 }
909 }
910
911
Saurav Das7bcbe702017-06-13 15:35:54 -0700912 // To save on ECMP groups
913 // avoid MPLS rules in non-edge-devices to non-edge-devices
914 // avoid MPLS transit rules in edge-devices
915 // avoid loopback IP rules in edge-devices to non-edge-devices
916 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700917 }
918
919 /**
920 * Processes a set a route-path changes by editing hash groups.
921 *
922 * @param routeChanges a set of route-path changes, where each route-path is
923 * a list with its first element the src-switch of the path
924 * and the second element the dst-switch of the path.
925 * @param linkOrSwitchFailed true if the route changes are for a failed
926 * switch or linkDown event
927 * @param failedSwitch the switchId if the route changes are for a failed switch,
928 * otherwise null
929 */
930 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
931 boolean linkOrSwitchFailed,
932 DeviceId failedSwitch) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700933 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
934 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700935 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700936 if (route.size() == 1) {
937 // route-path changes are from everyone else to this switch
938 DeviceId dstSw = route.get(0);
939 srManager.deviceService.getAvailableDevices().forEach(sw -> {
940 if (!sw.id().equals(dstSw)) {
941 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
942 }
943 });
944 } else {
945 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700946 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700947 }
Saurav Dasc568c342018-01-25 09:49:01 -0800948 boolean someFailed = false;
949 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Das9df5b7c2017-08-14 16:44:43 -0700950 for (ArrayList<DeviceId> route : changedRoutes) {
951 DeviceId targetSw = route.get(0);
952 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700953 if (linkOrSwitchFailed) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700954 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700955 // it's possible that we cannot fix hash groups for a route
956 // if the target switch has failed. Nevertheless the ecmp graph
957 // for the impacted switch must still be updated.
Saurav Das9df5b7c2017-08-14 16:44:43 -0700958 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700959 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
960 currentEcmpSpgMap.remove(targetSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700961 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -0700962 + "target:{}", dstSw, targetSw);
Saurav Dasc568c342018-01-25 09:49:01 -0800963 updatedDevices.add(targetSw);
964 updatedDevices.add(dstSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700965 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -0700966 }
967 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -0700968 if (success) {
969 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das9df5b7c2017-08-14 16:44:43 -0700970 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Dasc568c342018-01-25 09:49:01 -0800971 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
972 + " or switchdown", dstSw, targetSw);
973 updatedDevices.add(targetSw);
974 updatedDevices.add(dstSw);
975 } else {
976 someFailed = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700977 }
978 } else {
979 //linkup of seen before link
980 boolean success = fixHashGroupsForRoute(route, false);
981 if (success) {
982 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
983 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
984 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -0700985 targetSw, dstSw);
Saurav Dasc568c342018-01-25 09:49:01 -0800986 updatedDevices.add(targetSw);
987 updatedDevices.add(dstSw);
988 } else {
989 someFailed = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700990 }
991 }
992 }
Saurav Dasc568c342018-01-25 09:49:01 -0800993 if (!someFailed) {
994 // here is where we update all devices not touched by this instance
995 updatedEcmpSpgMap.keySet().stream()
996 .filter(devId -> !updatedDevices.contains(devId))
997 .forEach(devId -> {
998 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
999 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1000 });
1001 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001002 }
1003
1004 /**
1005 * Edits hash groups in the src-switch (targetSw) of a route-path by
1006 * calling the groupHandler to either add or remove buckets in an existing
1007 * hash group.
1008 *
1009 * @param route a single list representing a route-path where the first element
1010 * is the src-switch (targetSw) of the route-path and the
1011 * second element is the dst-switch
1012 * @param revoke true if buckets in the hash-groups need to be removed;
1013 * false if buckets in the hash-groups need to be added
1014 * @return true if the hash group editing is successful
1015 */
1016 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1017 boolean revoke) {
1018 DeviceId targetSw = route.get(0);
1019 if (route.size() < 2) {
1020 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1021 return false;
1022 }
1023 DeviceId destSw = route.get(1);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001024 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001025 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001026 // figure out the new next hops at the targetSw towards the destSw
Saurav Das9df5b7c2017-08-14 16:44:43 -07001027 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001028 // call group handler to change hash group at targetSw
1029 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1030 if (grpHandler == null) {
1031 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1032 + " {} hash group buckets for route:{} ", targetSw,
1033 (revoke) ? "revoke" : "repopulate", route);
1034 return false;
1035 }
1036 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
1037 (revoke) ? "revoke" : "repopulating",
1038 targetSw, destSw, nextHops);
1039 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1040 destSw, true)
1041 : grpHandler.fixHashGroups(targetSw, nextHops,
1042 destSw, false);
1043 }
1044
1045 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001046 * Start the flow rule population process if it was never started. The
1047 * process finishes successfully when all flow rules are set and stops with
1048 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -07001049 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001050 public void startPopulationProcess() {
1051 statusLock.lock();
1052 try {
1053 if (populationStatus == Status.IDLE
1054 || populationStatus == Status.SUCCEEDED
1055 || populationStatus == Status.ABORTED) {
1056 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -07001057 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -07001058 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1059 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001060 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001061 } finally {
1062 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001063 }
sangho20eff1d2015-04-13 15:15:58 -07001064 }
1065
Saurav Dasb5c236e2016-06-07 10:08:06 -07001066 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001067 * Revoke rules of given subnet in all edge switches.
1068 *
1069 * @param subnets subnet being removed
1070 * @return true if succeed
1071 */
1072 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1073 statusLock.lock();
1074 try {
1075 return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
1076 } finally {
1077 statusLock.unlock();
1078 }
1079 }
1080
1081 /**
Charles Chan2fde6d42017-08-23 14:46:43 -07001082 * Populates IP rules for a route that has direct connection to the switch
1083 * if the current instance is the master of the switch.
1084 *
1085 * @param deviceId device ID of the device that next hop attaches to
1086 * @param prefix IP prefix of the route
1087 * @param hostMac MAC address of the next hop
1088 * @param hostVlanId Vlan ID of the nexthop
1089 * @param outPort port where the next hop attaches to
1090 */
1091 void populateRoute(DeviceId deviceId, IpPrefix prefix,
1092 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
1093 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1094 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1095 }
1096 }
1097
1098 /**
1099 * Removes IP rules for a route when the next hop is gone.
1100 * if the current instance is the master of the switch.
1101 *
1102 * @param deviceId device ID of the device that next hop attaches to
1103 * @param prefix IP prefix of the route
1104 * @param hostMac MAC address of the next hop
1105 * @param hostVlanId Vlan ID of the nexthop
1106 * @param outPort port that next hop attaches to
1107 */
1108 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1109 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
1110 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1111 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1112 }
1113 }
1114
1115 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001116 * Remove ECMP graph entry for the given device. Typically called when
1117 * device is no longer available.
1118 *
1119 * @param deviceId the device for which graphs need to be purged
1120 */
1121 protected void purgeEcmpGraph(DeviceId deviceId) {
Saurav Dasc568c342018-01-25 09:49:01 -08001122 statusLock.lock();
1123 try {
1124
1125 if (populationStatus == Status.STARTED) {
1126 log.warn("Previous rule population is not finished. Cannot"
1127 + " proceeed with purgeEcmpGraph for {}", deviceId);
1128 return;
1129 }
1130 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1131 currentEcmpSpgMap.remove(deviceId);
1132 if (updatedEcmpSpgMap != null) {
1133 updatedEcmpSpgMap.remove(deviceId);
1134 }
1135 } finally {
1136 statusLock.unlock();
Saurav Das7bcbe702017-06-13 15:35:54 -07001137 }
1138 }
1139
1140 //////////////////////////////////////
1141 // Routing helper methods and classes
1142 //////////////////////////////////////
1143
1144 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001145 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001146 * previous ecmp shortest-path graph exists for a switch in order to compute
1147 * affected routes. If such a graph does not exist, the method returns null.
1148 *
1149 * @param linkFail the failed link
1150 * @return the set of affected routes which may be empty if no routes were
1151 * affected, or null if no previous ecmp spg was found for comparison
1152 */
sangho20eff1d2015-04-13 15:15:58 -07001153 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001154 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1155
1156 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001157 log.debug("Computing the impacted routes for device {} due to link fail",
1158 sw.id());
Saurav Das9df5b7c2017-08-14 16:44:43 -07001159 DeviceId retId = shouldHandleRouting(sw.id());
1160 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001161 continue;
1162 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001163 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1164 for (DeviceId rootSw : devicesToProcess) {
1165 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1166 if (ecmpSpg == null) {
1167 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1168 + " rerouting and opting for full-reroute", rootSw);
1169 return null;
1170 }
1171 if (log.isDebugEnabled()) {
1172 log.debug("Root switch: {}", rootSw);
1173 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1174 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1175 }
1176 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1177 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1178 // figure out if the broken link affected any route-paths in this graph
1179 for (Integer itrIdx : switchVia.keySet()) {
1180 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1181 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1182 switchVia.get(itrIdx);
1183 for (DeviceId targetSw : swViaMap.keySet()) {
1184 log.trace("TargetSwitch {} --> RootSwitch {}",
1185 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001186 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1187 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001188 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001189 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001190 Set<ArrayList<DeviceId>> subLinks =
1191 computeLinks(targetSw, rootSw, swViaMap);
1192 for (ArrayList<DeviceId> alink: subLinks) {
1193 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1194 alink.get(1).equals(linkFail.dst().deviceId()))
1195 ||
1196 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1197 alink.get(1).equals(linkFail.src().deviceId()))) {
1198 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1199 ArrayList<DeviceId> aRoute = new ArrayList<>();
1200 aRoute.add(targetSw); // switch with rules to populate
1201 aRoute.add(rootSw); // towards this destination
1202 routes.add(aRoute);
1203 break;
1204 }
sangho20eff1d2015-04-13 15:15:58 -07001205 }
1206 }
1207 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001208
sangho20eff1d2015-04-13 15:15:58 -07001209 }
sangho45b009c2015-05-07 13:30:57 -07001210
sangho20eff1d2015-04-13 15:15:58 -07001211 }
sangho20eff1d2015-04-13 15:15:58 -07001212 return routes;
1213 }
1214
Saurav Das4e3224f2016-11-29 14:27:25 -08001215 /**
1216 * Computes set of affected routes due to new links or failed switches.
1217 *
1218 * @return the set of affected routes which may be empty if no routes were
1219 * affected
1220 */
Saurav Dase0d4c872018-03-05 14:37:16 -08001221 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001222 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001223 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001224
1225 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001226 log.debug("Computing the impacted routes for device {}", sw.id());
1227 DeviceId retId = shouldHandleRouting(sw.id());
1228 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001229 continue;
1230 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001231 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1232 for (DeviceId rootSw : devicesToProcess) {
1233 if (log.isTraceEnabled()) {
1234 log.trace("Device links for dev: {}", rootSw);
1235 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1236 log.trace("{} -> {} ", link.src().deviceId(),
1237 link.dst().deviceId());
1238 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001239 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001240 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1241 if (currEcmpSpg == null) {
1242 log.debug("No existing ECMP graph for device {}.. adding self as "
1243 + "changed route", rootSw);
1244 changedRtBldr.add(Lists.newArrayList(rootSw));
1245 continue;
1246 }
1247 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
1248 if (log.isDebugEnabled()) {
1249 log.debug("Root switch: {}", rootSw);
1250 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1251 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1252 }
1253 // first use the updated/new map to compare to current/existing map
1254 // as new links may have come up
1255 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1256 // then use the current/existing map to compare to updated/new map
1257 // as switch may have been removed
1258 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001259 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001260 }
sangho20eff1d2015-04-13 15:15:58 -07001261
Saurav Dase0d4c872018-03-05 14:37:16 -08001262 // handle clearing state for a failed switch in case the switch does
1263 // not have a pair, or the pair is not available
1264 if (failedSwitch != null) {
1265 DeviceId pairDev = getPairDev(failedSwitch);
1266 if (pairDev == null || !srManager.deviceService.isAvailable(pairDev)) {
1267 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1268 srManager.deviceService.getDevices().forEach(dev -> {
1269 if (!dev.id().equals(failedSwitch) &&
1270 srManager.mastershipService.isLocalMaster(dev.id())) {
1271 log.debug(" : {}", dev.id());
1272 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1273 }
1274 });
1275 }
1276 }
1277
Saurav Das7bcbe702017-06-13 15:35:54 -07001278 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001279 for (ArrayList<DeviceId> route: changedRoutes) {
1280 log.debug("Route changes Target -> Root");
1281 if (route.size() == 1) {
1282 log.debug(" : all -> {}", route.get(0));
1283 } else {
1284 log.debug(" : {} -> {}", route.get(0), route.get(1));
1285 }
1286 }
1287 return changedRoutes;
1288 }
1289
1290 /**
1291 * For the root switch, searches all the target nodes reachable in the base
1292 * graph, and compares paths to the ones in the comp graph.
1293 *
1294 * @param base the graph that is indexed for all reachable target nodes
1295 * from the root node
1296 * @param comp the graph that the base graph is compared to
1297 * @param rootSw both ecmp graphs are calculated for the root node
1298 * @return all the routes that have changed in the base graph
1299 */
1300 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1301 EcmpShortestPathGraph comp,
1302 DeviceId rootSw) {
1303 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1304 ImmutableSet.builder();
1305 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1306 base.getAllLearnedSwitchesAndVia();
1307 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1308 comp.getAllLearnedSwitchesAndVia();
1309 for (Integer itrIdx : baseMap.keySet()) {
1310 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1311 baseMap.get(itrIdx);
1312 for (DeviceId targetSw : baseViaMap.keySet()) {
1313 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1314 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1315 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001316 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001317 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001318 route.add(targetSw); // switch with rules to populate
1319 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001320 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001321 }
1322 }
sangho45b009c2015-05-07 13:30:57 -07001323 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001324 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001325 }
1326
Saurav Das7bcbe702017-06-13 15:35:54 -07001327 /**
1328 * Returns the ECMP paths traversed to reach the target switch.
1329 *
1330 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1331 * @param targetSw the switch to reach from the root switch
1332 * @return the nodes traversed on ECMP paths to the target switch
1333 */
sangho20eff1d2015-04-13 15:15:58 -07001334 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001335 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001336 for (Integer itrIdx : switchVia.keySet()) {
1337 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1338 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001339 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001340 continue;
1341 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001342 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001343 }
1344 }
1345
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001346 return null;
sangho20eff1d2015-04-13 15:15:58 -07001347 }
1348
Saurav Das7bcbe702017-06-13 15:35:54 -07001349 /**
1350 * Utility method to break down a path from src to dst device into a collection
1351 * of links.
1352 *
1353 * @param src src device of the path
1354 * @param dst dst device of the path
1355 * @param viaMap path taken from src to dst device
1356 * @return collection of links in the path
1357 */
sangho20eff1d2015-04-13 15:15:58 -07001358 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1359 DeviceId dst,
1360 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1361 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1362 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1363 DeviceId linkSrc = src;
1364 DeviceId linkDst = dst;
1365 for (DeviceId viaDevice: via) {
1366 ArrayList<DeviceId> link = new ArrayList<>();
1367 linkDst = viaDevice;
1368 link.add(linkSrc);
1369 link.add(linkDst);
1370 subLinks.add(link);
1371 linkSrc = viaDevice;
1372 }
1373 ArrayList<DeviceId> link = new ArrayList<>();
1374 link.add(linkSrc);
1375 link.add(dst);
1376 subLinks.add(link);
1377 }
1378
1379 return subLinks;
1380 }
1381
Charles Chan93e71ba2016-04-29 14:38:22 -07001382 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001383 * Determines whether this controller instance should handle routing for the
1384 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
1385 * Returns null if this instance should not handle routing for given {@code deviceId}.
1386 * Otherwise the returned value could be the given deviceId itself, or the
1387 * deviceId for the paired edge device. In the latter case, this instance
1388 * should handle routing for both the given device and the paired device.
Charles Chan93e71ba2016-04-29 14:38:22 -07001389 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001390 * @param deviceId device identifier to consider for routing
1391 * @return null or deviceId which could be the same as the given deviceId
1392 * or the deviceId of a paired edge device
Charles Chan93e71ba2016-04-29 14:38:22 -07001393 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001394 private DeviceId shouldHandleRouting(DeviceId deviceId) {
1395 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1396 log.debug("Not master for dev:{} .. skipping routing, may get handled "
1397 + "elsewhere as part of paired devices", deviceId);
1398 return null;
1399 }
1400 NodeId myNode = srManager.mastershipService.getMasterFor(deviceId);
1401 DeviceId pairDev = getPairDev(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001402
Saurav Das7bcbe702017-06-13 15:35:54 -07001403 if (pairDev != null) {
1404 if (!srManager.deviceService.isAvailable(pairDev)) {
Saurav Dase0d4c872018-03-05 14:37:16 -08001405 log.warn("pairedDev {} not available .. routing both this dev:{} "
1406 + "and pair without mastership check for pair",
Saurav Das7bcbe702017-06-13 15:35:54 -07001407 pairDev, deviceId);
1408 return pairDev; // handle both temporarily
1409 }
1410 NodeId pairMasterNode = srManager.mastershipService.getMasterFor(pairDev);
1411 if (myNode.compareTo(pairMasterNode) <= 0) {
1412 log.debug("Handling routing for both dev:{} pair-dev:{}; myNode: {}"
1413 + " pairMaster:{} compare:{}", deviceId, pairDev,
1414 myNode, pairMasterNode,
1415 myNode.compareTo(pairMasterNode));
1416 return pairDev; // handle both
1417 } else {
1418 log.debug("PairDev node: {} should handle routing for dev:{} and "
1419 + "pair-dev:{}", pairMasterNode, deviceId, pairDev);
1420 return null; // handle neither
sanghob35a6192015-04-01 13:05:26 -07001421 }
1422 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001423 return deviceId; // not paired, just handle given device
sanghob35a6192015-04-01 13:05:26 -07001424 }
1425
Charles Chan93e71ba2016-04-29 14:38:22 -07001426 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001427 * Returns the configured paired DeviceId for the given Device, or null
1428 * if no such paired device has been configured.
Charles Chan93e71ba2016-04-29 14:38:22 -07001429 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001430 * @param deviceId
1431 * @return configured pair deviceId or null
Charles Chan93e71ba2016-04-29 14:38:22 -07001432 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001433 private DeviceId getPairDev(DeviceId deviceId) {
1434 DeviceId pairDev;
Charles Chan0b4e6182015-11-03 10:42:14 -08001435 try {
Saurav Das7bcbe702017-06-13 15:35:54 -07001436 pairDev = srManager.deviceConfiguration.getPairDeviceId(deviceId);
Charles Chan0b4e6182015-11-03 10:42:14 -08001437 } catch (DeviceConfigNotFoundException e) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001438 log.warn(e.getMessage() + " .. cannot continue routing for dev: {}");
1439 return null;
Charles Chan0b4e6182015-11-03 10:42:14 -08001440 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001441 return pairDev;
sanghob35a6192015-04-01 13:05:26 -07001442 }
1443
1444 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001445 * Returns the set of deviceIds which are the next hops from the targetSw
1446 * to the dstSw according to the latest ECMP spg.
1447 *
1448 * @param targetSw the switch for which the next-hops are desired
1449 * @param dstSw the switch to which the next-hops lead to from the targetSw
1450 * @return set of next hop deviceIds, could be empty if no next hops are found
1451 */
1452 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1453 boolean targetIsEdge = false;
1454 try {
1455 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1456 } catch (DeviceConfigNotFoundException e) {
1457 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1458 + "continuing to getNextHops", targetSw);
1459 }
1460
1461 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1462 if (ecmpSpg == null) {
1463 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1464 return ImmutableSet.of();
1465 }
1466 HashMap<Integer,
1467 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1468 ecmpSpg.getAllLearnedSwitchesAndVia();
1469 for (Integer itrIdx : switchVia.keySet()) {
1470 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1471 switchVia.get(itrIdx);
1472 for (DeviceId target : swViaMap.keySet()) {
1473 if (!target.equals(targetSw)) {
1474 continue;
1475 }
1476 if (!targetIsEdge && itrIdx > 1) {
Saurav Dasa4020382018-02-14 14:14:54 -08001477 // optimization for spines to not use leaves to get
1478 // to a spine or other leaves
1479 boolean pathdevIsEdge = false;
1480 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1481 for (DeviceId pathdev : via) {
1482 try {
1483 pathdevIsEdge = srManager.deviceConfiguration
1484 .isEdgeDevice(pathdev);
1485 } catch (DeviceConfigNotFoundException e) {
1486 log.warn(e.getMessage());
1487 }
1488 if (pathdevIsEdge) {
1489 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1490 + " --> dstSw:{} which goes through an edge"
1491 + " device {} in path {}", itrIdx,
1492 targetSw, dstSw, pathdev, via);
1493 return ImmutableSet.of();
1494 }
1495 }
1496 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001497 }
1498 Set<DeviceId> nextHops = new HashSet<>();
1499 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1500 if (via.isEmpty()) {
1501 // the dstSw is the next-hop from the targetSw
1502 nextHops.add(dstSw);
1503 } else {
1504 // first elem is next-hop in each ECMP path
1505 nextHops.add(via.get(0));
1506 }
1507 }
1508 return nextHops;
1509 }
1510 }
1511 return ImmutableSet.of(); //no next-hops found
1512 }
1513
1514 /**
1515 * Represents two devices that are paired by configuration. An EdgePair for
1516 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1517 */
1518 protected final class EdgePair {
1519 DeviceId dev1;
1520 DeviceId dev2;
1521
1522 EdgePair(DeviceId dev1, DeviceId dev2) {
1523 this.dev1 = dev1;
1524 this.dev2 = dev2;
1525 }
1526
1527 boolean includes(DeviceId dev) {
1528 return dev1.equals(dev) || dev2.equals(dev);
1529 }
1530
1531 @Override
1532 public boolean equals(Object o) {
1533 if (this == o) {
1534 return true;
1535 }
1536 if (!(o instanceof EdgePair)) {
1537 return false;
1538 }
1539 EdgePair that = (EdgePair) o;
1540 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1541 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1542 }
1543
1544 @Override
1545 public int hashCode() {
1546 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1547 return Objects.hash(dev1, dev2);
1548 } else {
1549 return Objects.hash(dev2, dev1);
1550 }
1551 }
1552
1553 @Override
1554 public String toString() {
1555 return toStringHelper(this)
1556 .add("Dev1", dev1)
1557 .add("Dev2", dev2)
1558 .toString();
1559 }
1560 }
1561
1562 //////////////////////////////////////
1563 // Filtering rule creation
1564 //////////////////////////////////////
1565
1566 /**
Saurav Das018605f2017-02-18 14:05:44 -08001567 * Populates filtering rules for port, and punting rules
1568 * for gateway IPs, loopback IPs and arp/ndp traffic.
1569 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001570 *
1571 * @param deviceId Switch ID to set the rules
1572 */
Saurav Das822c4e22015-10-23 10:51:11 -07001573 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001574 // Although device is added, sometimes device store does not have the
1575 // ports for this device yet. It results in missing filtering rules in the
1576 // switch. We will attempt it a few times. If it still does not work,
1577 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001578 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001579 if (firstRun == null) {
1580 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001581 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001582 executorService.schedule(new RetryFilters(deviceId, firstRun),
1583 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001584 }
1585
1586 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001587 * Utility class used to temporarily store information about the ports on a
1588 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001589 */
1590 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001591 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001592
Saurav Das018605f2017-02-18 14:05:44 -08001593 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001594 int filteredPorts) {
1595 this.disabledPorts = disabledPorts;
1596 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001597 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001598 }
1599
1600 @Override
1601 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001602 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001603 }
1604
1605 @Override
1606 public boolean equals(Object obj) {
1607 if (this == obj) {
1608 return true;
1609 }
1610 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1611 return false;
1612 }
1613 PortFilterInfo other = (PortFilterInfo) obj;
1614 return ((disabledPorts == other.disabledPorts) &&
1615 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001616 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001617 }
1618
1619 @Override
1620 public String toString() {
1621 MoreObjects.ToStringHelper helper = toStringHelper(this)
1622 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001623 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001624 .add("filteredPorts", filteredPorts);
1625 return helper.toString();
1626 }
1627 }
1628
1629 /**
1630 * RetryFilters populates filtering objectives for a device and keeps retrying
1631 * till the number of ports filtered are constant for a predefined number
1632 * of attempts.
1633 */
1634 protected final class RetryFilters implements Runnable {
1635 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1636 DeviceId devId;
1637 int counter;
1638 PortFilterInfo prevRun;
1639
1640 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001641 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001642 prevRun = previousRun;
1643 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001644 }
1645
1646 @Override
1647 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001648 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001649 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001650 boolean sameResult = prevRun.equals(thisRun);
1651 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1652 thisRun, sameResult);
Ray Milkeyc6c9b172018-02-26 09:36:31 -08001653 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001654 // exponentially increasing intervals for retries
1655 executorService.schedule(this,
1656 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1657 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001658 if (!sameResult) {
1659 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1660 }
Saurav Das59232cf2016-04-27 18:35:50 -07001661 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001662 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001663 }
Saurav Das59232cf2016-04-27 18:35:50 -07001664 }
1665
sanghob35a6192015-04-01 13:05:26 -07001666}