blob: 2da2df5def97997ed592447f2b723eccfa91d98f [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
26import org.joda.time.DateTime;
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 Chan23686832017-08-23 14:46:43 -070030import org.onlab.packet.MacAddress;
31import org.onlab.packet.VlanId;
Saurav Das7bcbe702017-06-13 15:35:54 -070032import org.onosproject.cluster.NodeId;
Charles Chan93e71ba2016-04-29 14:38:22 -070033import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070034import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070036import org.onosproject.net.Link;
Charles Chan23686832017-08-23 14:46:43 -070037import org.onosproject.net.PortNumber;
Charles Chan0b4e6182015-11-03 10:42:14 -080038import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
39import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070040import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
sanghob35a6192015-04-01 13:05:26 -070041import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43
44import 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;
sanghob35a6192015-04-01 13:05:26 -070055
Saurav Dasd2fded02016-12-02 15:43:47 -080056import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventree0ae7a32016-11-23 09:57:42 -080057import static com.google.common.base.Preconditions.checkNotNull;
58import static java.util.concurrent.Executors.newScheduledThreadPool;
59import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070060
Charles Chane849c192016-01-11 18:28:54 -080061/**
62 * Default routing handler that is responsible for route computing and
63 * routing rule population.
64 */
sanghob35a6192015-04-01 13:05:26 -070065public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080066 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
67 private static final int RETRY_INTERVAL_MS = 250;
68 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070069 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das041bb782017-08-14 16:44:43 -070070 private static final int UPDATE_INTERVAL = 5; //secs
Charles Chan93e71ba2016-04-29 14:38:22 -070071 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070072
73 private SegmentRoutingManager srManager;
74 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053075 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
76 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070077 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090078 private final Lock statusLock = new ReentrantLock();
79 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070080 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080081 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Dasceccf242017-08-03 18:30:35 -070082 private DateTime lastRoutingChange;
sanghob35a6192015-04-01 13:05:26 -070083
84 /**
85 * Represents the default routing population status.
86 */
87 public enum Status {
88 // population process is not started yet.
89 IDLE,
90
91 // population process started.
92 STARTED,
93
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -070094 // population process was aborted due to errors, mostly for groups not
95 // found.
sanghob35a6192015-04-01 13:05:26 -070096 ABORTED,
97
98 // population process was finished successfully.
99 SUCCEEDED
100 }
101
102 /**
103 * Creates a DefaultRoutingHandler object.
104 *
105 * @param srManager SegmentRoutingManager object
106 */
107 public DefaultRoutingHandler(SegmentRoutingManager srManager) {
108 this.srManager = srManager;
109 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700110 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700111 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700112 this.currentEcmpSpgMap = Maps.newHashMap();
sanghob35a6192015-04-01 13:05:26 -0700113 }
114
115 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700116 * Returns an immutable copy of the current ECMP shortest-path graph as
117 * computed by this controller instance.
118 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700119 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700120 */
121 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
122 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
123 currentEcmpSpgMap.entrySet().forEach(entry -> {
124 if (entry.getValue() != null) {
125 builder.put(entry.getKey(), entry.getValue());
126 }
127 });
128 return builder.build();
129 }
130
Saurav Dasceccf242017-08-03 18:30:35 -0700131 /**
132 * Acquires the lock used when making routing changes.
133 */
134 public void acquireRoutingLock() {
135 statusLock.lock();
136 }
137
138 /**
139 * Releases the lock used when making routing changes.
140 */
141 public void releaseRoutingLock() {
142 statusLock.unlock();
143 }
144
145 /**
146 * Determines if routing in the network has been stable in the last
147 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
148 * routing change timestamp.
149 *
150 * @return true if stable
151 */
152 public boolean isRoutingStable() {
153 long last = (long) (lastRoutingChange.getMillis() / 1000.0);
154 long now = (long) (DateTime.now().getMillis() / 1000.0);
Saurav Das041bb782017-08-14 16:44:43 -0700155 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700156 return (now - last) > STABLITY_THRESHOLD;
157 }
158
159
Saurav Das7bcbe702017-06-13 15:35:54 -0700160 //////////////////////////////////////
161 // Route path handling
162 //////////////////////////////////////
163
164 /* The following three methods represent the three major ways in routing
165 * is triggered in the network
166 * a) due to configuration change
167 * b) due to route-added event
168 * c) due to change in the topology
169 */
170
Saurav Dasc88d4662017-05-15 15:34:25 -0700171 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700172 * Populates all routing rules to all switches. Typically triggered at
173 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700174 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700175 public void populateAllRoutingRules() {
Saurav Dasceccf242017-08-03 18:30:35 -0700176 lastRoutingChange = DateTime.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900177 statusLock.lock();
178 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700179 if (populationStatus == Status.STARTED) {
180 log.warn("Previous rule population is not finished. Cannot"
181 + " proceed with populateAllRoutingRules");
182 return;
183 }
184
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900185 populationStatus = Status.STARTED;
186 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700187 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900188 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700189
Saurav Das7bcbe702017-06-13 15:35:54 -0700190 // take a snapshot of the topology
191 updatedEcmpSpgMap = new HashMap<>();
192 Set<EdgePair> edgePairs = new HashSet<>();
193 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
194 for (Device dstSw : srManager.deviceService.getDevices()) {
195 EcmpShortestPathGraph ecmpSpgUpdated =
196 new EcmpShortestPathGraph(dstSw.id(), srManager);
197 updatedEcmpSpgMap.put(dstSw.id(), ecmpSpgUpdated);
198 DeviceId pairDev = getPairDev(dstSw.id());
199 if (pairDev != null) {
200 // pairDev may not be available yet, but we still need to add
201 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
202 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
203 edgePairs.add(new EdgePair(dstSw.id(), pairDev));
204 }
205 DeviceId ret = shouldHandleRouting(dstSw.id());
206 if (ret == null) {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900207 continue;
208 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700209 Set<DeviceId> devsToProcess = Sets.newHashSet(dstSw.id(), ret);
210 // To do a full reroute, assume all routes have changed
211 for (DeviceId dev : devsToProcess) {
212 for (Device targetSw : srManager.deviceService.getDevices()) {
213 if (targetSw.id().equals(dev)) {
214 continue;
215 }
216 routeChanges.add(Lists.newArrayList(targetSw.id(), dev));
217 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900218 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700219 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900220
Saurav Das7bcbe702017-06-13 15:35:54 -0700221 if (!redoRouting(routeChanges, edgePairs, null)) {
222 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
223 populationStatus = Status.ABORTED;
224 log.warn("Failed to repopulate all routing rules.");
225 return;
sanghob35a6192015-04-01 13:05:26 -0700226 }
227
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900228 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
229 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700230 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900231 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700232 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900233 } finally {
234 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700235 }
sanghob35a6192015-04-01 13:05:26 -0700236 }
237
sangho20eff1d2015-04-13 15:15:58 -0700238 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700239 * Populate rules from all other edge devices to the connect-point(s)
240 * specified for the given subnets.
241 *
242 * @param cpts connect point(s) of the subnets being added
243 * @param subnets subnets being added
Charles Chan23686832017-08-23 14:46:43 -0700244 */
245 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700246 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan0e711b52017-09-11 15:21:57 -0700247 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
248 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
249 return;
250 }
251
Saurav Dasceccf242017-08-03 18:30:35 -0700252 lastRoutingChange = DateTime.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700253 statusLock.lock();
254 try {
255 if (populationStatus == Status.STARTED) {
256 log.warn("Previous rule population is not finished. Cannot"
257 + " proceed with routing rules for added routes");
258 return;
259 }
260 populationStatus = Status.STARTED;
261 rulePopulator.resetCounter();
Charles Chan23686832017-08-23 14:46:43 -0700262 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
263 subnets, cpts);
Saurav Das7bcbe702017-06-13 15:35:54 -0700264 // Take snapshots of the topology
265 updatedEcmpSpgMap = new HashMap<>();
266 Set<EdgePair> edgePairs = new HashSet<>();
267 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
268 boolean handleRouting = false;
269
270 if (cpts.size() == 2) {
271 // ensure connect points are edge-pairs
272 Iterator<ConnectPoint> iter = cpts.iterator();
273 DeviceId dev1 = iter.next().deviceId();
274 DeviceId pairDev = getPairDev(dev1);
275 if (iter.next().deviceId().equals(pairDev)) {
276 edgePairs.add(new EdgePair(dev1, pairDev));
277 } else {
278 log.warn("Connectpoints {} for subnets {} not on "
279 + "pair-devices.. aborting populateSubnet", cpts, subnets);
280 populationStatus = Status.ABORTED;
281 return;
282 }
283 for (ConnectPoint cp : cpts) {
284 EcmpShortestPathGraph ecmpSpgUpdated =
285 new EcmpShortestPathGraph(cp.deviceId(), srManager);
286 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
287 DeviceId retId = shouldHandleRouting(cp.deviceId());
288 if (retId == null) {
289 continue;
290 }
291 handleRouting = true;
292 }
293 } else {
294 // single connect point
295 DeviceId dstSw = cpts.iterator().next().deviceId();
296 EcmpShortestPathGraph ecmpSpgUpdated =
297 new EcmpShortestPathGraph(dstSw, srManager);
298 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
299 if (srManager.mastershipService.isLocalMaster(dstSw)) {
300 handleRouting = true;
301 }
302 }
303
304 if (!handleRouting) {
305 log.debug("This instance is not handling ecmp routing to the "
306 + "connectPoint(s) {}", cpts);
307 populationStatus = Status.ABORTED;
308 return;
309 }
310
311 // if it gets here, this instance should handle routing for the
312 // connectpoint(s). Assume all route-paths have to be updated to
313 // the connectpoint(s) with the following exceptions
314 // 1. if target is non-edge no need for routing rules
315 // 2. if target is one of the connectpoints
316 for (ConnectPoint cp : cpts) {
317 DeviceId dstSw = cp.deviceId();
318 for (Device targetSw : srManager.deviceService.getDevices()) {
319 boolean isEdge = false;
320 try {
321 isEdge = config.isEdgeDevice(targetSw.id());
322 } catch (DeviceConfigNotFoundException e) {
323 log.warn(e.getMessage() + "aborting populateSubnet");
324 populationStatus = Status.ABORTED;
325 return;
326 }
327 if (dstSw.equals(targetSw.id()) || !isEdge ||
328 (cpts.size() == 2 &&
329 targetSw.id().equals(getPairDev(dstSw)))) {
330 continue;
331 }
332 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
333 }
334 }
335
336 if (!redoRouting(routeChanges, edgePairs, subnets)) {
337 log.debug("populateSubnet: populationStatus is ABORTED");
338 populationStatus = Status.ABORTED;
339 log.warn("Failed to repopulate the rules for subnet.");
340 return;
341 }
342
343 log.debug("populateSubnet: populationStatus is SUCCEEDED");
344 populationStatus = Status.SUCCEEDED;
345 log.info("Completed subnet population. Total # of rules pushed : {}",
346 rulePopulator.getCounter());
347 return;
348
349 } finally {
350 statusLock.unlock();
351 }
352 }
353
354 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700355 * Populates the routing rules or makes hash group changes according to the
356 * route-path changes due to link failure, switch failure or link up. This
357 * method should only be called for one of these three possible event-types.
358 * Note that when a switch goes away, all of its links fail as well,
359 * but this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700360 *
Saurav Dasc88d4662017-05-15 15:34:25 -0700361 * @param linkDown the single failed link, or null for other conditions
362 * such as link-up or a removed switch
363 * @param linkUp the single link up, or null for other conditions such as
364 * link-down or a removed switch
365 * @param switchDown the removed switch, or null for other conditions such as
366 * link-down or link-up
Saurav Das7bcbe702017-06-13 15:35:54 -0700367 */ // refactor
Saurav Dasc88d4662017-05-15 15:34:25 -0700368 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
369 Link linkUp,
370 DeviceId switchDown) {
371 if ((linkDown != null && (linkUp != null || switchDown != null)) ||
372 (linkUp != null && (linkDown != null || switchDown != null)) ||
373 (switchDown != null && (linkUp != null || linkDown != null))) {
374 log.warn("Only one event can be handled for link status change .. aborting");
375 return;
376 }
Saurav Dasceccf242017-08-03 18:30:35 -0700377 lastRoutingChange = DateTime.now();
Saurav Das041bb782017-08-14 16:44:43 -0700378 executorService.schedule(new UpdateMaps(), UPDATE_INTERVAL,
379 TimeUnit.SECONDS);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900380 statusLock.lock();
381 try {
sangho20eff1d2015-04-13 15:15:58 -0700382
383 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700384 log.warn("Previous rule population is not finished. Cannot"
385 + " proceeed with routingRules for Link Status change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700386 return;
sangho20eff1d2015-04-13 15:15:58 -0700387 }
388
Saurav Das7bcbe702017-06-13 15:35:54 -0700389 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700390 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700391 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700392 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530393 EcmpShortestPathGraph ecmpSpgUpdated =
394 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700395 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Saurav Das7bcbe702017-06-13 15:35:54 -0700396 DeviceId pairDev = getPairDev(sw.id());
397 if (pairDev != null) {
398 // pairDev may not be available yet, but we still need to add
399 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
400 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
401 edgePairs.add(new EdgePair(sw.id(), pairDev));
402 }
sangho45b009c2015-05-07 13:30:57 -0700403 }
404
Saurav Das7bcbe702017-06-13 15:35:54 -0700405 log.info("Starting to populate routing rules from link status change");
sangho52abe3a2015-05-05 14:13:34 -0700406
sangho20eff1d2015-04-13 15:15:58 -0700407 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700408 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700409 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700410 populationStatus = Status.STARTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700411 rulePopulator.resetCounter();
Saurav Das4e3224f2016-11-29 14:27:25 -0800412 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700413 if (linkDown == null) {
414 // either a linkUp or a switchDown - compute all route changes by
415 // comparing all routes of existing ECMP SPG to new ECMP SPG
sangho20eff1d2015-04-13 15:15:58 -0700416 routeChanges = computeRouteChange();
Saurav Dasc88d4662017-05-15 15:34:25 -0700417
Saurav Das041bb782017-08-14 16:44:43 -0700418 // deal with linkUp of a seen-before link
419 if (linkUp != null && srManager.isSeenLink(linkUp)) {
420 if (!srManager.isBidirectional(linkUp)) {
421 log.warn("Not a bidirectional link yet .. not "
422 + "processing link {}", linkUp);
423 srManager.updateSeenLink(linkUp, true);
424 populationStatus = Status.ABORTED;
425 return;
Saurav Dasc88d4662017-05-15 15:34:25 -0700426 }
Saurav Das041bb782017-08-14 16:44:43 -0700427 // link previously seen before
428 // do hash-bucket changes instead of a re-route
429 processHashGroupChange(routeChanges, false, null);
430 // clear out routesChanges so a re-route is not attempted
431 routeChanges = ImmutableSet.of();
Saurav Dasc88d4662017-05-15 15:34:25 -0700432 }
Saurav Das041bb782017-08-14 16:44:43 -0700433 // for a linkUp of a never-seen-before link
434 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700435
436 // now that we are past the check for a previously seen link
437 // it is safe to update the store for the linkUp
438 if (linkUp != null) {
439 srManager.updateSeenLink(linkUp, true);
440 }
441
Saurav Das041bb782017-08-14 16:44:43 -0700442 //deal with switchDown
443 if (switchDown != null) {
444 processHashGroupChange(routeChanges, true, switchDown);
445 // clear out routesChanges so a re-route is not attempted
446 routeChanges = ImmutableSet.of();
447 }
sangho20eff1d2015-04-13 15:15:58 -0700448 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700449 // link has gone down
450 // Compare existing ECMP SPG only with the link that went down
451 routeChanges = computeDamagedRoutes(linkDown);
452 if (routeChanges != null) {
453 processHashGroupChange(routeChanges, true, null);
454 // clear out routesChanges so a re-route is not attempted
455 routeChanges = ImmutableSet.of();
456 }
sangho20eff1d2015-04-13 15:15:58 -0700457 }
458
Saurav Das4e3224f2016-11-29 14:27:25 -0800459 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700460 if (routeChanges == null) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700461 log.info("Optimized routing failed... opting for full reroute");
462 populationStatus = Status.ABORTED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700463 populateAllRoutingRules();
464 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700465 }
466
sangho20eff1d2015-04-13 15:15:58 -0700467 if (routeChanges.isEmpty()) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700468 log.info("No re-route attempted for the link status change");
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700469 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700470 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700471 return;
sangho20eff1d2015-04-13 15:15:58 -0700472 }
473
Saurav Dasc88d4662017-05-15 15:34:25 -0700474 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700475 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700476 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700477 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700478 log.info("Completed repopulation of rules for link-status change."
479 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700480 return;
sangho20eff1d2015-04-13 15:15:58 -0700481 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700482 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700483 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700484 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700485 return;
sangho20eff1d2015-04-13 15:15:58 -0700486 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900487 } finally {
488 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700489 }
490 }
491
Saurav Das041bb782017-08-14 16:44:43 -0700492
Saurav Dasc88d4662017-05-15 15:34:25 -0700493 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700494 * Processes a set a route-path changes by reprogramming routing rules and
495 * creating new hash-groups or editing them if necessary. This method also
496 * determines the next-hops for the route-path from the src-switch (target)
497 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700498 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700499 * @param routeChanges a set of route-path changes, where each route-path is
500 * a list with its first element the src-switch (target)
501 * of the path, and the second element the dst-switch of
502 * the path.
503 * @param edgePairs a set of edge-switches that are paired by configuration
504 * @param subnets a set of prefixes that need to be populated in the routing
505 * table of the target switch in the route-path. Can be null,
506 * in which case all the prefixes belonging to the dst-switch
507 * will be populated in the target switch
508 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700509 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700510 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
511 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
512 // first make every entry two-elements
513 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
514 for (ArrayList<DeviceId> route : routeChanges) {
515 if (route.size() == 1) {
516 DeviceId dstSw = route.get(0);
517 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
518 if (ec == null) {
519 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
520 return false;
521 }
522 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
523 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
524 changedRoutes.add(Lists.newArrayList(target, dstSw));
525 });
526 });
527 } else {
528 DeviceId targetSw = route.get(0);
529 DeviceId dstSw = route.get(1);
530 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
531 }
532 }
533
534 // now process changedRoutes according to edgePairs
535 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
536 return false; //abort routing and fail fast
537 }
538
539 // whatever is left in changedRoutes is now processed for individual dsts.
540 if (!redoRoutingIndividualDests(subnets, changedRoutes)) {
541 return false; //abort routing and fail fast
542 }
543
Saurav Das7bcbe702017-06-13 15:35:54 -0700544 // update ecmpSPG for all edge-pairs
545 for (EdgePair ep : edgePairs) {
546 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
547 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
548 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
549 }
550 return true;
551 }
552
553 /**
554 * Programs targetSw in the changedRoutes for given prefixes reachable by
555 * an edgePair. If no prefixes are given, the method will use configured
556 * subnets/prefixes. If some configured subnets belong only to a specific
557 * destination in the edgePair, then the target switch will be programmed
558 * only to that destination.
559 *
560 * @param edgePairs set of edge-pairs for which target will be programmed
561 * @param subnets a set of prefixes that need to be populated in the routing
562 * table of the target switch in the changedRoutes. Can be null,
563 * in which case all the configured prefixes belonging to the
564 * paired switches will be populated in the target switch
565 * @param changedRoutes a set of route-path changes, where each route-path is
566 * a list with its first element the src-switch (target)
567 * of the path, and the second element the dst-switch of
568 * the path.
569 * @return true if successful
570 */
571 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
572 Set<IpPrefix> subnets,
573 Set<ArrayList<DeviceId>> changedRoutes) {
574 for (EdgePair ep : edgePairs) {
575 // temp store for a target's changedRoutes to this edge-pair
576 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
577 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
578 while (i.hasNext()) {
579 ArrayList<DeviceId> route = i.next();
580 DeviceId dstSw = route.get(1);
581 if (ep.includes(dstSw)) {
582 // routeChange for edge pair found
583 // sort by target iff target is edge and remove from changedRoutes
584 DeviceId targetSw = route.get(0);
585 try {
586 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
587 continue;
588 }
589 } catch (DeviceConfigNotFoundException e) {
590 log.warn(e.getMessage() + "aborting redoRouting");
591 return false;
592 }
593 // route is from another edge to this edge-pair
594 if (targetRoutes.containsKey(targetSw)) {
595 targetRoutes.get(targetSw).add(route);
596 } else {
597 Set<ArrayList<DeviceId>> temp = new HashSet<>();
598 temp.add(route);
599 targetRoutes.put(targetSw, temp);
600 }
601 i.remove();
602 }
603 }
604 // so now for this edgepair we have a per target set of routechanges
605 // process target->edgePair route
606 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
607 targetRoutes.entrySet()) {
608 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
609 entry.getKey(), ep);
610 DeviceId targetSw = entry.getKey();
611 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
612 entry.getValue().forEach(route -> {
613 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
614 log.debug("route: target {} -> dst {} found with next-hops {}",
615 route.get(0), route.get(1), nhops);
616 perDstNextHops.put(route.get(1), nhops);
617 });
618 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
619 : subnets;
620 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
621 : subnets;
622 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
623 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
624 // handle routing to subnets common to edge-pair
625 // only if the targetSw is not part of the edge-pair
626 if (!ep.includes(targetSw)) {
627 if (!populateEcmpRoutingRulePartial(
628 targetSw,
629 ep.dev1, ep.dev2,
630 perDstNextHops,
631 Sets.intersection(ipDev1, ipDev2))) {
632 return false; // abort everything and fail fast
633 }
634 }
635 // handle routing to subnets that only belong to dev1
636 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
637 if (!onlyDev1Subnets.isEmpty() && perDstNextHops.get(ep.dev1) != null) {
638 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
639 onlyDev1NextHops.put(ep.dev1, perDstNextHops.get(ep.dev1));
640 if (!populateEcmpRoutingRulePartial(
641 targetSw,
642 ep.dev1, null,
643 onlyDev1NextHops,
644 onlyDev1Subnets)) {
645 return false; // abort everything and fail fast
646 }
647 }
648 // handle routing to subnets that only belong to dev2
649 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
650 if (!onlyDev2Subnets.isEmpty() && perDstNextHops.get(ep.dev2) != null) {
651 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
652 onlyDev2NextHops.put(ep.dev2, perDstNextHops.get(ep.dev2));
653 if (!populateEcmpRoutingRulePartial(
654 targetSw,
655 ep.dev2, null,
656 onlyDev2NextHops,
657 onlyDev2Subnets)) {
658 return false; // abort everything and fail fast
659 }
660 }
661 }
662 // if it gets here it has succeeded for all targets to this edge-pair
663 }
664 return true;
665 }
666
667 /**
668 * Programs targetSw in the changedRoutes for given prefixes reachable by
669 * a destination switch that is not part of an edge-pair.
670 * If no prefixes are given, the method will use configured subnets/prefixes.
671 *
672 * @param subnets a set of prefixes that need to be populated in the routing
673 * table of the target switch in the changedRoutes. Can be null,
674 * in which case all the configured prefixes belonging to the
675 * paired switches will be populated in the target switch
676 * @param changedRoutes a set of route-path changes, where each route-path is
677 * a list with its first element the src-switch (target)
678 * of the path, and the second element the dst-switch of
679 * the path.
680 * @return true if successful
681 */
682 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
683 Set<ArrayList<DeviceId>> changedRoutes) {
684 // aggregate route-path changes for each dst device
685 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
686 new HashMap<>();
687 for (ArrayList<DeviceId> route: changedRoutes) {
688 DeviceId dstSw = route.get(1);
689 ArrayList<ArrayList<DeviceId>> deviceRoutes =
690 routesBydevice.get(dstSw);
691 if (deviceRoutes == null) {
692 deviceRoutes = new ArrayList<>();
693 routesBydevice.put(dstSw, deviceRoutes);
694 }
695 deviceRoutes.add(route);
696 }
697 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
698 ArrayList<ArrayList<DeviceId>> deviceRoutes =
699 routesBydevice.get(impactedDstDevice);
700 for (ArrayList<DeviceId> route: deviceRoutes) {
701 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
702 route.get(0), route.get(1));
703 DeviceId targetSw = route.get(0);
704 DeviceId dstSw = route.get(1); // same as impactedDstDevice
705 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Das607c8982018-01-09 17:38:44 -0800706 if (nextHops.isEmpty()) {
707 log.warn("Could not find next hop from target:{} --> dst {} "
708 + "skipping this route", targetSw, dstSw);
709 continue;
710 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700711 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
712 nhops.put(dstSw, nextHops);
713 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
714 (subnets == null) ? Sets.newHashSet() : subnets)) {
715 return false; // abort routing and fail fast
716 }
717 log.debug("Populating flow rules from target: {} to dst: {}"
718 + " is successful", targetSw, dstSw);
719 }
720 //Only if all the flows for all impacted routes to a
721 //specific target are pushed successfully, update the
722 //ECMP graph for that target. Or else the next event
723 //would not see any changes in the ECMP graphs.
724 //In another case, the target switch has gone away, so
725 //routes can't be installed. In that case, the current map
726 //is updated here, without any flows being pushed.
727 currentEcmpSpgMap.put(impactedDstDevice,
728 updatedEcmpSpgMap.get(impactedDstDevice));
729 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
730 }
731 return true;
732 }
733
734 /**
735 * Populate ECMP rules for subnets from target to destination via nexthops.
736 *
737 * @param targetSw Device ID of target switch in which rules will be programmed
738 * @param destSw1 Device ID of final destination switch to which the rules will forward
739 * @param destSw2 Device ID of paired destination switch to which the rules will forward
740 * A null deviceId indicates packets should only be sent to destSw1
741 * @param nextHops Map indication a list of next hops per destSw
742 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
743 * @return true if it succeeds in populating rules
744 */ // refactor
745 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
746 DeviceId destSw1,
747 DeviceId destSw2,
748 Map<DeviceId, Set<DeviceId>> nextHops,
749 Set<IpPrefix> subnets) {
750 boolean result;
751 // If both target switch and dest switch are edge routers, then set IP
752 // rule for both subnet and router IP.
753 boolean targetIsEdge;
754 boolean dest1IsEdge;
755 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
756 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
757
758 try {
759 targetIsEdge = config.isEdgeDevice(targetSw);
760 dest1IsEdge = config.isEdgeDevice(destSw1);
761 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
762 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
763 if (destSw2 != null) {
764 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
765 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
766 }
767 } catch (DeviceConfigNotFoundException e) {
768 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700769 return false;
770 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700771
772 if (targetIsEdge && dest1IsEdge) {
773 subnets = (subnets != null && !subnets.isEmpty())
774 ? Sets.newHashSet(subnets)
775 : Sets.newHashSet(config.getSubnets(destSw1));
776 // XXX - Rethink this
777 /*subnets.add(dest1RouterIpv4.toIpPrefix());
778 if (dest1RouterIpv6 != null) {
779 subnets.add(dest1RouterIpv6.toIpPrefix());
780 }
781 if (destSw2 != null && dest2RouterIpv4 != null) {
782 subnets.add(dest2RouterIpv4.toIpPrefix());
783 if (dest2RouterIpv6 != null) {
784 subnets.add(dest2RouterIpv6.toIpPrefix());
785 }
786 }*/
787 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
788 + "for subnets {}", targetSw, destSw1,
789 (destSw2 != null) ? ("& " + destSw2) : "",
790 subnets);
791 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
792 destSw1, destSw2,
793 nextHops);
794 if (!result) {
795 return false;
796 }
797 /* XXX rethink this
798 IpPrefix routerIpPrefix = destRouterIpv4.toIpPrefix();
799 log.debug("* populateEcmpRoutingRulePartial in device {} towards {} "
800 + "for router IP {}", targetSw, destSw, routerIpPrefix);
801 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
802 destSw, nextHops);
803 if (!result) {
804 return false;
805 }
806 // If present we deal with IPv6 loopback.
807 if (destRouterIpv6 != null) {
808 routerIpPrefix = destRouterIpv6.toIpPrefix();
809 log.debug("* populateEcmpRoutingRulePartial in device {} towards {}"
810 + " for v6 router IP {}", targetSw, destSw, routerIpPrefix);
811 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
812 destSw, nextHops);
813 if (!result) {
814 return false;
815 }
816 }*/
Saurav Dasc88d4662017-05-15 15:34:25 -0700817 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700818
819 if (!targetIsEdge && dest1IsEdge) {
820 // MPLS rules in all non-edge target devices. These rules are for
821 // individual destinations, even if the dsts are part of edge-pairs.
822 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
823 + "all MPLS rules", targetSw, destSw1);
824 result = rulePopulator.populateMplsRule(targetSw, destSw1,
825 nextHops.get(destSw1),
826 dest1RouterIpv4);
827 if (!result) {
828 return false;
829 }
830 if (dest1RouterIpv6 != null) {
831 result = rulePopulator.populateMplsRule(targetSw, destSw1,
832 nextHops.get(destSw1),
833 dest1RouterIpv6);
834 if (!result) {
835 return false;
836 }
837 }
838 }
839
840 // To save on ECMP groups
841 // avoid MPLS rules in non-edge-devices to non-edge-devices
842 // avoid MPLS transit rules in edge-devices
843 // avoid loopback IP rules in edge-devices to non-edge-devices
844 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700845 }
846
847 /**
848 * Processes a set a route-path changes by editing hash groups.
849 *
850 * @param routeChanges a set of route-path changes, where each route-path is
851 * a list with its first element the src-switch of the path
852 * and the second element the dst-switch of the path.
853 * @param linkOrSwitchFailed true if the route changes are for a failed
854 * switch or linkDown event
855 * @param failedSwitch the switchId if the route changes are for a failed switch,
856 * otherwise null
857 */
858 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
859 boolean linkOrSwitchFailed,
860 DeviceId failedSwitch) {
Saurav Das041bb782017-08-14 16:44:43 -0700861 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
862 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700863 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das041bb782017-08-14 16:44:43 -0700864 if (route.size() == 1) {
865 // route-path changes are from everyone else to this switch
866 DeviceId dstSw = route.get(0);
867 srManager.deviceService.getAvailableDevices().forEach(sw -> {
868 if (!sw.id().equals(dstSw)) {
869 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
870 }
871 });
872 } else {
873 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700874 }
Saurav Das041bb782017-08-14 16:44:43 -0700875 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700876
Saurav Das041bb782017-08-14 16:44:43 -0700877 for (ArrayList<DeviceId> route : changedRoutes) {
878 DeviceId targetSw = route.get(0);
879 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700880 if (linkOrSwitchFailed) {
Saurav Das041bb782017-08-14 16:44:43 -0700881 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700882 // it's possible that we cannot fix hash groups for a route
883 // if the target switch has failed. Nevertheless the ecmp graph
884 // for the impacted switch must still be updated.
Saurav Das041bb782017-08-14 16:44:43 -0700885 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700886 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
887 currentEcmpSpgMap.remove(targetSw);
Saurav Das041bb782017-08-14 16:44:43 -0700888 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -0700889 + "target:{}", dstSw, targetSw);
Saurav Das041bb782017-08-14 16:44:43 -0700890 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -0700891 }
892 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -0700893 if (success) {
894 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das041bb782017-08-14 16:44:43 -0700895 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
896 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown",
897 dstSw, targetSw);
898 }
899 } else {
900 //linkup of seen before link
901 boolean success = fixHashGroupsForRoute(route, false);
902 if (success) {
903 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
904 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
905 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -0700906 targetSw, dstSw);
907 }
908 }
909 }
910 }
911
912 /**
913 * Edits hash groups in the src-switch (targetSw) of a route-path by
914 * calling the groupHandler to either add or remove buckets in an existing
915 * hash group.
916 *
917 * @param route a single list representing a route-path where the first element
918 * is the src-switch (targetSw) of the route-path and the
919 * second element is the dst-switch
920 * @param revoke true if buckets in the hash-groups need to be removed;
921 * false if buckets in the hash-groups need to be added
922 * @return true if the hash group editing is successful
923 */
924 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
925 boolean revoke) {
926 DeviceId targetSw = route.get(0);
927 if (route.size() < 2) {
928 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
929 return false;
930 }
931 DeviceId destSw = route.get(1);
Saurav Das041bb782017-08-14 16:44:43 -0700932 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -0700933 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -0700934 // figure out the new next hops at the targetSw towards the destSw
Saurav Das041bb782017-08-14 16:44:43 -0700935 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -0700936 // call group handler to change hash group at targetSw
937 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
938 if (grpHandler == null) {
939 log.warn("Cannot find grouphandler for dev:{} .. aborting"
940 + " {} hash group buckets for route:{} ", targetSw,
941 (revoke) ? "revoke" : "repopulate", route);
942 return false;
943 }
944 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
945 (revoke) ? "revoke" : "repopulating",
946 targetSw, destSw, nextHops);
947 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
948 destSw, true)
949 : grpHandler.fixHashGroups(targetSw, nextHops,
950 destSw, false);
951 }
952
953 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700954 * Start the flow rule population process if it was never started. The
955 * process finishes successfully when all flow rules are set and stops with
956 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -0700957 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700958 public void startPopulationProcess() {
959 statusLock.lock();
960 try {
961 if (populationStatus == Status.IDLE
962 || populationStatus == Status.SUCCEEDED
963 || populationStatus == Status.ABORTED) {
964 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -0700965 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -0700966 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
967 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700968 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700969 } finally {
970 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700971 }
sangho20eff1d2015-04-13 15:15:58 -0700972 }
973
Saurav Dasb5c236e2016-06-07 10:08:06 -0700974 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700975 * Revoke rules of given subnet in all edge switches.
976 *
977 * @param subnets subnet being removed
978 * @return true if succeed
979 */
980 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
981 statusLock.lock();
982 try {
983 return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
984 } finally {
985 statusLock.unlock();
986 }
987 }
988
989 /**
Charles Chan23686832017-08-23 14:46:43 -0700990 * Populates IP rules for a route that has direct connection to the switch
991 * if the current instance is the master of the switch.
992 *
993 * @param deviceId device ID of the device that next hop attaches to
994 * @param prefix IP prefix of the route
995 * @param hostMac MAC address of the next hop
996 * @param hostVlanId Vlan ID of the nexthop
997 * @param outPort port where the next hop attaches to
998 */
999 void populateRoute(DeviceId deviceId, IpPrefix prefix,
1000 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
1001 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1002 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1003 }
1004 }
1005
1006 /**
1007 * Removes IP rules for a route when the next hop is gone.
1008 * if the current instance is the master of the switch.
1009 *
1010 * @param deviceId device ID of the device that next hop attaches to
1011 * @param prefix IP prefix of the route
1012 * @param hostMac MAC address of the next hop
1013 * @param hostVlanId Vlan ID of the nexthop
1014 * @param outPort port that next hop attaches to
1015 */
1016 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1017 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
1018 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1019 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1020 }
1021 }
1022
1023 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001024 * Remove ECMP graph entry for the given device. Typically called when
1025 * device is no longer available.
1026 *
1027 * @param deviceId the device for which graphs need to be purged
1028 */
1029 protected void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das041bb782017-08-14 16:44:43 -07001030 currentEcmpSpgMap.remove(deviceId); // XXX reconsider
Saurav Das7bcbe702017-06-13 15:35:54 -07001031 if (updatedEcmpSpgMap != null) {
1032 updatedEcmpSpgMap.remove(deviceId);
1033 }
1034 }
1035
1036 //////////////////////////////////////
1037 // Routing helper methods and classes
1038 //////////////////////////////////////
1039
1040 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001041 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001042 * previous ecmp shortest-path graph exists for a switch in order to compute
1043 * affected routes. If such a graph does not exist, the method returns null.
1044 *
1045 * @param linkFail the failed link
1046 * @return the set of affected routes which may be empty if no routes were
1047 * affected, or null if no previous ecmp spg was found for comparison
1048 */
sangho20eff1d2015-04-13 15:15:58 -07001049 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001050 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1051
1052 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001053 log.debug("Computing the impacted routes for device {} due to link fail",
1054 sw.id());
Saurav Das041bb782017-08-14 16:44:43 -07001055 DeviceId retId = shouldHandleRouting(sw.id());
1056 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001057 continue;
1058 }
Saurav Das041bb782017-08-14 16:44:43 -07001059 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1060 for (DeviceId rootSw : devicesToProcess) {
1061 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1062 if (ecmpSpg == null) {
1063 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1064 + " rerouting and opting for full-reroute", rootSw);
1065 return null;
1066 }
1067 if (log.isDebugEnabled()) {
1068 log.debug("Root switch: {}", rootSw);
1069 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1070 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1071 }
1072 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1073 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1074 // figure out if the broken link affected any route-paths in this graph
1075 for (Integer itrIdx : switchVia.keySet()) {
1076 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1077 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1078 switchVia.get(itrIdx);
1079 for (DeviceId targetSw : swViaMap.keySet()) {
1080 log.trace("TargetSwitch {} --> RootSwitch {}",
1081 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001082 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1083 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001084 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001085 }
Saurav Das041bb782017-08-14 16:44:43 -07001086 Set<ArrayList<DeviceId>> subLinks =
1087 computeLinks(targetSw, rootSw, swViaMap);
1088 for (ArrayList<DeviceId> alink: subLinks) {
1089 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1090 alink.get(1).equals(linkFail.dst().deviceId()))
1091 ||
1092 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1093 alink.get(1).equals(linkFail.src().deviceId()))) {
1094 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1095 ArrayList<DeviceId> aRoute = new ArrayList<>();
1096 aRoute.add(targetSw); // switch with rules to populate
1097 aRoute.add(rootSw); // towards this destination
1098 routes.add(aRoute);
1099 break;
1100 }
sangho20eff1d2015-04-13 15:15:58 -07001101 }
1102 }
1103 }
Saurav Das041bb782017-08-14 16:44:43 -07001104
sangho20eff1d2015-04-13 15:15:58 -07001105 }
sangho45b009c2015-05-07 13:30:57 -07001106
sangho20eff1d2015-04-13 15:15:58 -07001107 }
sangho20eff1d2015-04-13 15:15:58 -07001108 return routes;
1109 }
1110
Saurav Das4e3224f2016-11-29 14:27:25 -08001111 /**
1112 * Computes set of affected routes due to new links or failed switches.
1113 *
1114 * @return the set of affected routes which may be empty if no routes were
1115 * affected
1116 */
sangho20eff1d2015-04-13 15:15:58 -07001117 private Set<ArrayList<DeviceId>> computeRouteChange() {
Saurav Das7bcbe702017-06-13 15:35:54 -07001118 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001119 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001120
1121 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001122 log.debug("Computing the impacted routes for device {}", sw.id());
1123 DeviceId retId = shouldHandleRouting(sw.id());
1124 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001125 continue;
1126 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001127 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1128 for (DeviceId rootSw : devicesToProcess) {
1129 if (log.isTraceEnabled()) {
1130 log.trace("Device links for dev: {}", rootSw);
1131 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1132 log.trace("{} -> {} ", link.src().deviceId(),
1133 link.dst().deviceId());
1134 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001135 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001136 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1137 if (currEcmpSpg == null) {
1138 log.debug("No existing ECMP graph for device {}.. adding self as "
1139 + "changed route", rootSw);
1140 changedRtBldr.add(Lists.newArrayList(rootSw));
1141 continue;
1142 }
1143 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
1144 if (log.isDebugEnabled()) {
1145 log.debug("Root switch: {}", rootSw);
1146 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1147 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1148 }
1149 // first use the updated/new map to compare to current/existing map
1150 // as new links may have come up
1151 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1152 // then use the current/existing map to compare to updated/new map
1153 // as switch may have been removed
1154 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001155 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001156 }
sangho20eff1d2015-04-13 15:15:58 -07001157
Saurav Das7bcbe702017-06-13 15:35:54 -07001158 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001159 for (ArrayList<DeviceId> route: changedRoutes) {
1160 log.debug("Route changes Target -> Root");
1161 if (route.size() == 1) {
1162 log.debug(" : all -> {}", route.get(0));
1163 } else {
1164 log.debug(" : {} -> {}", route.get(0), route.get(1));
1165 }
1166 }
1167 return changedRoutes;
1168 }
1169
1170 /**
1171 * For the root switch, searches all the target nodes reachable in the base
1172 * graph, and compares paths to the ones in the comp graph.
1173 *
1174 * @param base the graph that is indexed for all reachable target nodes
1175 * from the root node
1176 * @param comp the graph that the base graph is compared to
1177 * @param rootSw both ecmp graphs are calculated for the root node
1178 * @return all the routes that have changed in the base graph
1179 */
1180 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1181 EcmpShortestPathGraph comp,
1182 DeviceId rootSw) {
1183 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1184 ImmutableSet.builder();
1185 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1186 base.getAllLearnedSwitchesAndVia();
1187 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1188 comp.getAllLearnedSwitchesAndVia();
1189 for (Integer itrIdx : baseMap.keySet()) {
1190 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1191 baseMap.get(itrIdx);
1192 for (DeviceId targetSw : baseViaMap.keySet()) {
1193 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1194 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1195 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001196 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001197 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001198 route.add(targetSw); // switch with rules to populate
1199 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001200 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001201 }
1202 }
sangho45b009c2015-05-07 13:30:57 -07001203 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001204 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001205 }
1206
Saurav Das7bcbe702017-06-13 15:35:54 -07001207 /**
1208 * Returns the ECMP paths traversed to reach the target switch.
1209 *
1210 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1211 * @param targetSw the switch to reach from the root switch
1212 * @return the nodes traversed on ECMP paths to the target switch
1213 */
sangho20eff1d2015-04-13 15:15:58 -07001214 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001215 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001216 for (Integer itrIdx : switchVia.keySet()) {
1217 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1218 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001219 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001220 continue;
1221 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001222 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001223 }
1224 }
1225
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001226 return null;
sangho20eff1d2015-04-13 15:15:58 -07001227 }
1228
Saurav Das7bcbe702017-06-13 15:35:54 -07001229 /**
1230 * Utility method to break down a path from src to dst device into a collection
1231 * of links.
1232 *
1233 * @param src src device of the path
1234 * @param dst dst device of the path
1235 * @param viaMap path taken from src to dst device
1236 * @return collection of links in the path
1237 */
sangho20eff1d2015-04-13 15:15:58 -07001238 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1239 DeviceId dst,
1240 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1241 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1242 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1243 DeviceId linkSrc = src;
1244 DeviceId linkDst = dst;
1245 for (DeviceId viaDevice: via) {
1246 ArrayList<DeviceId> link = new ArrayList<>();
1247 linkDst = viaDevice;
1248 link.add(linkSrc);
1249 link.add(linkDst);
1250 subLinks.add(link);
1251 linkSrc = viaDevice;
1252 }
1253 ArrayList<DeviceId> link = new ArrayList<>();
1254 link.add(linkSrc);
1255 link.add(dst);
1256 subLinks.add(link);
1257 }
1258
1259 return subLinks;
1260 }
1261
Charles Chan93e71ba2016-04-29 14:38:22 -07001262 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001263 * Determines whether this controller instance should handle routing for the
1264 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
1265 * Returns null if this instance should not handle routing for given {@code deviceId}.
1266 * Otherwise the returned value could be the given deviceId itself, or the
1267 * deviceId for the paired edge device. In the latter case, this instance
1268 * should handle routing for both the given device and the paired device.
Charles Chan93e71ba2016-04-29 14:38:22 -07001269 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001270 * @param deviceId device identifier to consider for routing
1271 * @return null or deviceId which could be the same as the given deviceId
1272 * or the deviceId of a paired edge device
Charles Chan93e71ba2016-04-29 14:38:22 -07001273 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001274 private DeviceId shouldHandleRouting(DeviceId deviceId) {
1275 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1276 log.debug("Not master for dev:{} .. skipping routing, may get handled "
1277 + "elsewhere as part of paired devices", deviceId);
1278 return null;
1279 }
1280 NodeId myNode = srManager.mastershipService.getMasterFor(deviceId);
1281 DeviceId pairDev = getPairDev(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001282
Saurav Das7bcbe702017-06-13 15:35:54 -07001283 if (pairDev != null) {
1284 if (!srManager.deviceService.isAvailable(pairDev)) {
1285 log.warn("pairedDev {} not available .. routing this dev:{} "
1286 + "without mastership check",
1287 pairDev, deviceId);
1288 return pairDev; // handle both temporarily
1289 }
1290 NodeId pairMasterNode = srManager.mastershipService.getMasterFor(pairDev);
1291 if (myNode.compareTo(pairMasterNode) <= 0) {
1292 log.debug("Handling routing for both dev:{} pair-dev:{}; myNode: {}"
1293 + " pairMaster:{} compare:{}", deviceId, pairDev,
1294 myNode, pairMasterNode,
1295 myNode.compareTo(pairMasterNode));
1296 return pairDev; // handle both
1297 } else {
1298 log.debug("PairDev node: {} should handle routing for dev:{} and "
1299 + "pair-dev:{}", pairMasterNode, deviceId, pairDev);
1300 return null; // handle neither
sanghob35a6192015-04-01 13:05:26 -07001301 }
1302 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001303 return deviceId; // not paired, just handle given device
sanghob35a6192015-04-01 13:05:26 -07001304 }
1305
Charles Chan93e71ba2016-04-29 14:38:22 -07001306 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001307 * Returns the configured paired DeviceId for the given Device, or null
1308 * if no such paired device has been configured.
Charles Chan93e71ba2016-04-29 14:38:22 -07001309 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001310 * @param deviceId
1311 * @return configured pair deviceId or null
Charles Chan93e71ba2016-04-29 14:38:22 -07001312 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001313 private DeviceId getPairDev(DeviceId deviceId) {
1314 DeviceId pairDev;
Charles Chan0b4e6182015-11-03 10:42:14 -08001315 try {
Saurav Das7bcbe702017-06-13 15:35:54 -07001316 pairDev = srManager.deviceConfiguration.getPairDeviceId(deviceId);
Charles Chan0b4e6182015-11-03 10:42:14 -08001317 } catch (DeviceConfigNotFoundException e) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001318 log.warn(e.getMessage() + " .. cannot continue routing for dev: {}");
1319 return null;
Charles Chan0b4e6182015-11-03 10:42:14 -08001320 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001321 return pairDev;
sanghob35a6192015-04-01 13:05:26 -07001322 }
1323
1324 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001325 * Returns the set of deviceIds which are the next hops from the targetSw
1326 * to the dstSw according to the latest ECMP spg.
1327 *
1328 * @param targetSw the switch for which the next-hops are desired
1329 * @param dstSw the switch to which the next-hops lead to from the targetSw
1330 * @return set of next hop deviceIds, could be empty if no next hops are found
1331 */
1332 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1333 boolean targetIsEdge = false;
1334 try {
1335 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1336 } catch (DeviceConfigNotFoundException e) {
1337 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1338 + "continuing to getNextHops", targetSw);
1339 }
1340
1341 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1342 if (ecmpSpg == null) {
1343 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1344 return ImmutableSet.of();
1345 }
1346 HashMap<Integer,
1347 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1348 ecmpSpg.getAllLearnedSwitchesAndVia();
1349 for (Integer itrIdx : switchVia.keySet()) {
1350 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1351 switchVia.get(itrIdx);
1352 for (DeviceId target : swViaMap.keySet()) {
1353 if (!target.equals(targetSw)) {
1354 continue;
1355 }
1356 if (!targetIsEdge && itrIdx > 1) {
1357 // optimization for spines to not use other leaves to get
1358 // to a leaf to avoid loops
1359 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1360 + " --> dstSw:{}", itrIdx, targetSw, dstSw);
1361 break;
1362 }
1363 Set<DeviceId> nextHops = new HashSet<>();
1364 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1365 if (via.isEmpty()) {
1366 // the dstSw is the next-hop from the targetSw
1367 nextHops.add(dstSw);
1368 } else {
1369 // first elem is next-hop in each ECMP path
1370 nextHops.add(via.get(0));
1371 }
1372 }
1373 return nextHops;
1374 }
1375 }
1376 return ImmutableSet.of(); //no next-hops found
1377 }
1378
1379 /**
1380 * Represents two devices that are paired by configuration. An EdgePair for
1381 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1382 */
1383 protected final class EdgePair {
1384 DeviceId dev1;
1385 DeviceId dev2;
1386
1387 EdgePair(DeviceId dev1, DeviceId dev2) {
1388 this.dev1 = dev1;
1389 this.dev2 = dev2;
1390 }
1391
1392 boolean includes(DeviceId dev) {
1393 return dev1.equals(dev) || dev2.equals(dev);
1394 }
1395
1396 @Override
1397 public boolean equals(Object o) {
1398 if (this == o) {
1399 return true;
1400 }
1401 if (!(o instanceof EdgePair)) {
1402 return false;
1403 }
1404 EdgePair that = (EdgePair) o;
1405 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1406 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1407 }
1408
1409 @Override
1410 public int hashCode() {
1411 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1412 return Objects.hash(dev1, dev2);
1413 } else {
1414 return Objects.hash(dev2, dev1);
1415 }
1416 }
1417
1418 @Override
1419 public String toString() {
1420 return toStringHelper(this)
1421 .add("Dev1", dev1)
1422 .add("Dev2", dev2)
1423 .toString();
1424 }
1425 }
1426
Saurav Das041bb782017-08-14 16:44:43 -07001427 /**
1428 * Updates the currentEcmpSpgGraph for all devices.
1429 */
1430 private void updateEcmpSpgMaps() {
1431 for (Device sw : srManager.deviceService.getDevices()) {
1432 EcmpShortestPathGraph ecmpSpgUpdated =
1433 new EcmpShortestPathGraph(sw.id(), srManager);
1434 currentEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
1435 }
1436 }
1437
1438 /**
1439 * Ensures routing is stable before updating all ECMP SPG graphs.
1440 *
1441 * TODO: CORD-1843 will ensure maps are updated faster, potentially while
1442 * topology/routing is still unstable
1443 */
1444 private final class UpdateMaps implements Runnable {
1445 @Override
1446 public void run() {
1447 if (isRoutingStable()) {
1448 updateEcmpSpgMaps();
1449 } else {
1450 executorService.schedule(new UpdateMaps(), UPDATE_INTERVAL,
1451 TimeUnit.SECONDS);
1452 }
1453 }
1454 }
1455
Saurav Das7bcbe702017-06-13 15:35:54 -07001456 //////////////////////////////////////
1457 // Filtering rule creation
1458 //////////////////////////////////////
1459
1460 /**
Saurav Das018605f2017-02-18 14:05:44 -08001461 * Populates filtering rules for port, and punting rules
1462 * for gateway IPs, loopback IPs and arp/ndp traffic.
1463 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001464 *
1465 * @param deviceId Switch ID to set the rules
1466 */
Saurav Das822c4e22015-10-23 10:51:11 -07001467 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001468 // Although device is added, sometimes device store does not have the
1469 // ports for this device yet. It results in missing filtering rules in the
1470 // switch. We will attempt it a few times. If it still does not work,
1471 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001472 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001473 if (firstRun == null) {
1474 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001475 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001476 executorService.schedule(new RetryFilters(deviceId, firstRun),
1477 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001478 }
1479
1480 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001481 * Utility class used to temporarily store information about the ports on a
1482 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001483 */
1484 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001485 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001486
Saurav Das018605f2017-02-18 14:05:44 -08001487 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001488 int filteredPorts) {
1489 this.disabledPorts = disabledPorts;
1490 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001491 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001492 }
1493
1494 @Override
1495 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001496 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001497 }
1498
1499 @Override
1500 public boolean equals(Object obj) {
1501 if (this == obj) {
1502 return true;
1503 }
1504 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1505 return false;
1506 }
1507 PortFilterInfo other = (PortFilterInfo) obj;
1508 return ((disabledPorts == other.disabledPorts) &&
1509 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001510 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001511 }
1512
1513 @Override
1514 public String toString() {
1515 MoreObjects.ToStringHelper helper = toStringHelper(this)
1516 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001517 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001518 .add("filteredPorts", filteredPorts);
1519 return helper.toString();
1520 }
1521 }
1522
1523 /**
1524 * RetryFilters populates filtering objectives for a device and keeps retrying
1525 * till the number of ports filtered are constant for a predefined number
1526 * of attempts.
1527 */
1528 protected final class RetryFilters implements Runnable {
1529 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1530 DeviceId devId;
1531 int counter;
1532 PortFilterInfo prevRun;
1533
1534 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001535 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001536 prevRun = previousRun;
1537 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001538 }
1539
1540 @Override
1541 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001542 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001543 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001544 boolean sameResult = prevRun.equals(thisRun);
1545 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1546 thisRun, sameResult);
1547 if (thisRun == null || !sameResult || (sameResult && --constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001548 // exponentially increasing intervals for retries
1549 executorService.schedule(this,
1550 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1551 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001552 if (!sameResult) {
1553 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1554 }
Saurav Das59232cf2016-04-27 18:35:50 -07001555 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001556 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001557 }
Saurav Das59232cf2016-04-27 18:35:50 -07001558 }
1559
sanghob35a6192015-04-01 13:05:26 -07001560}