blob: 0d263e7607feb9c57a613af55f8022354e8f5006 [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) {
Saurav Dasceccf242017-08-03 18:30:35 -0700247 lastRoutingChange = DateTime.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700248 statusLock.lock();
249 try {
250 if (populationStatus == Status.STARTED) {
251 log.warn("Previous rule population is not finished. Cannot"
252 + " proceed with routing rules for added routes");
253 return;
254 }
255 populationStatus = Status.STARTED;
256 rulePopulator.resetCounter();
Charles Chan23686832017-08-23 14:46:43 -0700257 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
258 subnets, cpts);
Saurav Das7bcbe702017-06-13 15:35:54 -0700259 // Take snapshots of the topology
260 updatedEcmpSpgMap = new HashMap<>();
261 Set<EdgePair> edgePairs = new HashSet<>();
262 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
263 boolean handleRouting = false;
264
265 if (cpts.size() == 2) {
266 // ensure connect points are edge-pairs
267 Iterator<ConnectPoint> iter = cpts.iterator();
268 DeviceId dev1 = iter.next().deviceId();
269 DeviceId pairDev = getPairDev(dev1);
270 if (iter.next().deviceId().equals(pairDev)) {
271 edgePairs.add(new EdgePair(dev1, pairDev));
272 } else {
273 log.warn("Connectpoints {} for subnets {} not on "
274 + "pair-devices.. aborting populateSubnet", cpts, subnets);
275 populationStatus = Status.ABORTED;
276 return;
277 }
278 for (ConnectPoint cp : cpts) {
279 EcmpShortestPathGraph ecmpSpgUpdated =
280 new EcmpShortestPathGraph(cp.deviceId(), srManager);
281 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
282 DeviceId retId = shouldHandleRouting(cp.deviceId());
283 if (retId == null) {
284 continue;
285 }
286 handleRouting = true;
287 }
288 } else {
289 // single connect point
290 DeviceId dstSw = cpts.iterator().next().deviceId();
291 EcmpShortestPathGraph ecmpSpgUpdated =
292 new EcmpShortestPathGraph(dstSw, srManager);
293 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
294 if (srManager.mastershipService.isLocalMaster(dstSw)) {
295 handleRouting = true;
296 }
297 }
298
299 if (!handleRouting) {
300 log.debug("This instance is not handling ecmp routing to the "
301 + "connectPoint(s) {}", cpts);
302 populationStatus = Status.ABORTED;
303 return;
304 }
305
306 // if it gets here, this instance should handle routing for the
307 // connectpoint(s). Assume all route-paths have to be updated to
308 // the connectpoint(s) with the following exceptions
309 // 1. if target is non-edge no need for routing rules
310 // 2. if target is one of the connectpoints
311 for (ConnectPoint cp : cpts) {
312 DeviceId dstSw = cp.deviceId();
313 for (Device targetSw : srManager.deviceService.getDevices()) {
314 boolean isEdge = false;
315 try {
316 isEdge = config.isEdgeDevice(targetSw.id());
317 } catch (DeviceConfigNotFoundException e) {
318 log.warn(e.getMessage() + "aborting populateSubnet");
319 populationStatus = Status.ABORTED;
320 return;
321 }
322 if (dstSw.equals(targetSw.id()) || !isEdge ||
323 (cpts.size() == 2 &&
324 targetSw.id().equals(getPairDev(dstSw)))) {
325 continue;
326 }
327 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
328 }
329 }
330
331 if (!redoRouting(routeChanges, edgePairs, subnets)) {
332 log.debug("populateSubnet: populationStatus is ABORTED");
333 populationStatus = Status.ABORTED;
334 log.warn("Failed to repopulate the rules for subnet.");
335 return;
336 }
337
338 log.debug("populateSubnet: populationStatus is SUCCEEDED");
339 populationStatus = Status.SUCCEEDED;
340 log.info("Completed subnet population. Total # of rules pushed : {}",
341 rulePopulator.getCounter());
342 return;
343
344 } finally {
345 statusLock.unlock();
346 }
347 }
348
349 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700350 * Populates the routing rules or makes hash group changes according to the
351 * route-path changes due to link failure, switch failure or link up. This
352 * method should only be called for one of these three possible event-types.
353 * Note that when a switch goes away, all of its links fail as well,
354 * but this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700355 *
Saurav Dasc88d4662017-05-15 15:34:25 -0700356 * @param linkDown the single failed link, or null for other conditions
357 * such as link-up or a removed switch
358 * @param linkUp the single link up, or null for other conditions such as
359 * link-down or a removed switch
360 * @param switchDown the removed switch, or null for other conditions such as
361 * link-down or link-up
Saurav Das7bcbe702017-06-13 15:35:54 -0700362 */ // refactor
Saurav Dasc88d4662017-05-15 15:34:25 -0700363 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
364 Link linkUp,
365 DeviceId switchDown) {
366 if ((linkDown != null && (linkUp != null || switchDown != null)) ||
367 (linkUp != null && (linkDown != null || switchDown != null)) ||
368 (switchDown != null && (linkUp != null || linkDown != null))) {
369 log.warn("Only one event can be handled for link status change .. aborting");
370 return;
371 }
Saurav Dasceccf242017-08-03 18:30:35 -0700372 lastRoutingChange = DateTime.now();
Saurav Das041bb782017-08-14 16:44:43 -0700373 executorService.schedule(new UpdateMaps(), UPDATE_INTERVAL,
374 TimeUnit.SECONDS);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900375 statusLock.lock();
376 try {
sangho20eff1d2015-04-13 15:15:58 -0700377
378 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700379 log.warn("Previous rule population is not finished. Cannot"
380 + " proceeed with routingRules for Link Status change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700381 return;
sangho20eff1d2015-04-13 15:15:58 -0700382 }
383
Saurav Das7bcbe702017-06-13 15:35:54 -0700384 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700385 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700386 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700387 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530388 EcmpShortestPathGraph ecmpSpgUpdated =
389 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700390 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Saurav Das7bcbe702017-06-13 15:35:54 -0700391 DeviceId pairDev = getPairDev(sw.id());
392 if (pairDev != null) {
393 // pairDev may not be available yet, but we still need to add
394 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev, srManager);
395 updatedEcmpSpgMap.put(pairDev, ecmpSpgUpdated);
396 edgePairs.add(new EdgePair(sw.id(), pairDev));
397 }
sangho45b009c2015-05-07 13:30:57 -0700398 }
399
Saurav Das7bcbe702017-06-13 15:35:54 -0700400 log.info("Starting to populate routing rules from link status change");
sangho52abe3a2015-05-05 14:13:34 -0700401
sangho20eff1d2015-04-13 15:15:58 -0700402 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700403 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700404 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700405 populationStatus = Status.STARTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700406 rulePopulator.resetCounter();
Saurav Das4e3224f2016-11-29 14:27:25 -0800407 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700408 if (linkDown == null) {
409 // either a linkUp or a switchDown - compute all route changes by
410 // comparing all routes of existing ECMP SPG to new ECMP SPG
sangho20eff1d2015-04-13 15:15:58 -0700411 routeChanges = computeRouteChange();
Saurav Dasc88d4662017-05-15 15:34:25 -0700412
Saurav Das041bb782017-08-14 16:44:43 -0700413 // deal with linkUp of a seen-before link
414 if (linkUp != null && srManager.isSeenLink(linkUp)) {
415 if (!srManager.isBidirectional(linkUp)) {
416 log.warn("Not a bidirectional link yet .. not "
417 + "processing link {}", linkUp);
418 srManager.updateSeenLink(linkUp, true);
419 populationStatus = Status.ABORTED;
420 return;
Saurav Dasc88d4662017-05-15 15:34:25 -0700421 }
Saurav Das041bb782017-08-14 16:44:43 -0700422 // link previously seen before
423 // do hash-bucket changes instead of a re-route
424 processHashGroupChange(routeChanges, false, null);
425 // clear out routesChanges so a re-route is not attempted
426 routeChanges = ImmutableSet.of();
Saurav Dasc88d4662017-05-15 15:34:25 -0700427 }
Saurav Das041bb782017-08-14 16:44:43 -0700428 // for a linkUp of a never-seen-before link
429 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700430
431 // now that we are past the check for a previously seen link
432 // it is safe to update the store for the linkUp
433 if (linkUp != null) {
434 srManager.updateSeenLink(linkUp, true);
435 }
436
Saurav Das041bb782017-08-14 16:44:43 -0700437 //deal with switchDown
438 if (switchDown != null) {
439 processHashGroupChange(routeChanges, true, switchDown);
440 // clear out routesChanges so a re-route is not attempted
441 routeChanges = ImmutableSet.of();
442 }
sangho20eff1d2015-04-13 15:15:58 -0700443 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700444 // link has gone down
445 // Compare existing ECMP SPG only with the link that went down
446 routeChanges = computeDamagedRoutes(linkDown);
447 if (routeChanges != null) {
448 processHashGroupChange(routeChanges, true, null);
449 // clear out routesChanges so a re-route is not attempted
450 routeChanges = ImmutableSet.of();
451 }
sangho20eff1d2015-04-13 15:15:58 -0700452 }
453
Saurav Das4e3224f2016-11-29 14:27:25 -0800454 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700455 if (routeChanges == null) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700456 log.info("Optimized routing failed... opting for full reroute");
457 populationStatus = Status.ABORTED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700458 populateAllRoutingRules();
459 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700460 }
461
sangho20eff1d2015-04-13 15:15:58 -0700462 if (routeChanges.isEmpty()) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700463 log.info("No re-route attempted for the link status change");
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700464 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700465 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700466 return;
sangho20eff1d2015-04-13 15:15:58 -0700467 }
468
Saurav Dasc88d4662017-05-15 15:34:25 -0700469 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700470 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700471 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700472 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700473 log.info("Completed repopulation of rules for link-status change."
474 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700475 return;
sangho20eff1d2015-04-13 15:15:58 -0700476 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700477 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700478 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700479 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700480 return;
sangho20eff1d2015-04-13 15:15:58 -0700481 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900482 } finally {
483 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700484 }
485 }
486
Saurav Das041bb782017-08-14 16:44:43 -0700487
Saurav Dasc88d4662017-05-15 15:34:25 -0700488 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700489 * Processes a set a route-path changes by reprogramming routing rules and
490 * creating new hash-groups or editing them if necessary. This method also
491 * determines the next-hops for the route-path from the src-switch (target)
492 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700493 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700494 * @param routeChanges a set of route-path changes, where each route-path is
495 * a list with its first element the src-switch (target)
496 * of the path, and the second element the dst-switch of
497 * the path.
498 * @param edgePairs a set of edge-switches that are paired by configuration
499 * @param subnets a set of prefixes that need to be populated in the routing
500 * table of the target switch in the route-path. Can be null,
501 * in which case all the prefixes belonging to the dst-switch
502 * will be populated in the target switch
503 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700504 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700505 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
506 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
507 // first make every entry two-elements
508 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
509 for (ArrayList<DeviceId> route : routeChanges) {
510 if (route.size() == 1) {
511 DeviceId dstSw = route.get(0);
512 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
513 if (ec == null) {
514 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
515 return false;
516 }
517 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
518 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
519 changedRoutes.add(Lists.newArrayList(target, dstSw));
520 });
521 });
522 } else {
523 DeviceId targetSw = route.get(0);
524 DeviceId dstSw = route.get(1);
525 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
526 }
527 }
528
529 // now process changedRoutes according to edgePairs
530 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
531 return false; //abort routing and fail fast
532 }
533
534 // whatever is left in changedRoutes is now processed for individual dsts.
535 if (!redoRoutingIndividualDests(subnets, changedRoutes)) {
536 return false; //abort routing and fail fast
537 }
538
Saurav Das7bcbe702017-06-13 15:35:54 -0700539 // update ecmpSPG for all edge-pairs
540 for (EdgePair ep : edgePairs) {
541 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
542 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
543 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
544 }
545 return true;
546 }
547
548 /**
549 * Programs targetSw in the changedRoutes for given prefixes reachable by
550 * an edgePair. If no prefixes are given, the method will use configured
551 * subnets/prefixes. If some configured subnets belong only to a specific
552 * destination in the edgePair, then the target switch will be programmed
553 * only to that destination.
554 *
555 * @param edgePairs set of edge-pairs for which target will be programmed
556 * @param subnets a set of prefixes that need to be populated in the routing
557 * table of the target switch in the changedRoutes. Can be null,
558 * in which case all the configured prefixes belonging to the
559 * paired switches will be populated in the target switch
560 * @param changedRoutes a set of route-path changes, where each route-path is
561 * a list with its first element the src-switch (target)
562 * of the path, and the second element the dst-switch of
563 * the path.
564 * @return true if successful
565 */
566 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
567 Set<IpPrefix> subnets,
568 Set<ArrayList<DeviceId>> changedRoutes) {
569 for (EdgePair ep : edgePairs) {
570 // temp store for a target's changedRoutes to this edge-pair
571 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
572 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
573 while (i.hasNext()) {
574 ArrayList<DeviceId> route = i.next();
575 DeviceId dstSw = route.get(1);
576 if (ep.includes(dstSw)) {
577 // routeChange for edge pair found
578 // sort by target iff target is edge and remove from changedRoutes
579 DeviceId targetSw = route.get(0);
580 try {
581 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
582 continue;
583 }
584 } catch (DeviceConfigNotFoundException e) {
585 log.warn(e.getMessage() + "aborting redoRouting");
586 return false;
587 }
588 // route is from another edge to this edge-pair
589 if (targetRoutes.containsKey(targetSw)) {
590 targetRoutes.get(targetSw).add(route);
591 } else {
592 Set<ArrayList<DeviceId>> temp = new HashSet<>();
593 temp.add(route);
594 targetRoutes.put(targetSw, temp);
595 }
596 i.remove();
597 }
598 }
599 // so now for this edgepair we have a per target set of routechanges
600 // process target->edgePair route
601 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
602 targetRoutes.entrySet()) {
603 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
604 entry.getKey(), ep);
605 DeviceId targetSw = entry.getKey();
606 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
607 entry.getValue().forEach(route -> {
608 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
609 log.debug("route: target {} -> dst {} found with next-hops {}",
610 route.get(0), route.get(1), nhops);
611 perDstNextHops.put(route.get(1), nhops);
612 });
613 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
614 : subnets;
615 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
616 : subnets;
617 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
618 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
619 // handle routing to subnets common to edge-pair
620 // only if the targetSw is not part of the edge-pair
621 if (!ep.includes(targetSw)) {
622 if (!populateEcmpRoutingRulePartial(
623 targetSw,
624 ep.dev1, ep.dev2,
625 perDstNextHops,
626 Sets.intersection(ipDev1, ipDev2))) {
627 return false; // abort everything and fail fast
628 }
629 }
630 // handle routing to subnets that only belong to dev1
631 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
632 if (!onlyDev1Subnets.isEmpty() && perDstNextHops.get(ep.dev1) != null) {
633 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
634 onlyDev1NextHops.put(ep.dev1, perDstNextHops.get(ep.dev1));
635 if (!populateEcmpRoutingRulePartial(
636 targetSw,
637 ep.dev1, null,
638 onlyDev1NextHops,
639 onlyDev1Subnets)) {
640 return false; // abort everything and fail fast
641 }
642 }
643 // handle routing to subnets that only belong to dev2
644 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
645 if (!onlyDev2Subnets.isEmpty() && perDstNextHops.get(ep.dev2) != null) {
646 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
647 onlyDev2NextHops.put(ep.dev2, perDstNextHops.get(ep.dev2));
648 if (!populateEcmpRoutingRulePartial(
649 targetSw,
650 ep.dev2, null,
651 onlyDev2NextHops,
652 onlyDev2Subnets)) {
653 return false; // abort everything and fail fast
654 }
655 }
656 }
657 // if it gets here it has succeeded for all targets to this edge-pair
658 }
659 return true;
660 }
661
662 /**
663 * Programs targetSw in the changedRoutes for given prefixes reachable by
664 * a destination switch that is not part of an edge-pair.
665 * If no prefixes are given, the method will use configured subnets/prefixes.
666 *
667 * @param subnets a set of prefixes that need to be populated in the routing
668 * table of the target switch in the changedRoutes. Can be null,
669 * in which case all the configured prefixes belonging to the
670 * paired switches will be populated in the target switch
671 * @param changedRoutes a set of route-path changes, where each route-path is
672 * a list with its first element the src-switch (target)
673 * of the path, and the second element the dst-switch of
674 * the path.
675 * @return true if successful
676 */
677 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
678 Set<ArrayList<DeviceId>> changedRoutes) {
679 // aggregate route-path changes for each dst device
680 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
681 new HashMap<>();
682 for (ArrayList<DeviceId> route: changedRoutes) {
683 DeviceId dstSw = route.get(1);
684 ArrayList<ArrayList<DeviceId>> deviceRoutes =
685 routesBydevice.get(dstSw);
686 if (deviceRoutes == null) {
687 deviceRoutes = new ArrayList<>();
688 routesBydevice.put(dstSw, deviceRoutes);
689 }
690 deviceRoutes.add(route);
691 }
692 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
693 ArrayList<ArrayList<DeviceId>> deviceRoutes =
694 routesBydevice.get(impactedDstDevice);
695 for (ArrayList<DeviceId> route: deviceRoutes) {
696 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
697 route.get(0), route.get(1));
698 DeviceId targetSw = route.get(0);
699 DeviceId dstSw = route.get(1); // same as impactedDstDevice
700 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
701 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
702 nhops.put(dstSw, nextHops);
703 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
704 (subnets == null) ? Sets.newHashSet() : subnets)) {
705 return false; // abort routing and fail fast
706 }
707 log.debug("Populating flow rules from target: {} to dst: {}"
708 + " is successful", targetSw, dstSw);
709 }
710 //Only if all the flows for all impacted routes to a
711 //specific target are pushed successfully, update the
712 //ECMP graph for that target. Or else the next event
713 //would not see any changes in the ECMP graphs.
714 //In another case, the target switch has gone away, so
715 //routes can't be installed. In that case, the current map
716 //is updated here, without any flows being pushed.
717 currentEcmpSpgMap.put(impactedDstDevice,
718 updatedEcmpSpgMap.get(impactedDstDevice));
719 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
720 }
721 return true;
722 }
723
724 /**
725 * Populate ECMP rules for subnets from target to destination via nexthops.
726 *
727 * @param targetSw Device ID of target switch in which rules will be programmed
728 * @param destSw1 Device ID of final destination switch to which the rules will forward
729 * @param destSw2 Device ID of paired destination switch to which the rules will forward
730 * A null deviceId indicates packets should only be sent to destSw1
731 * @param nextHops Map indication a list of next hops per destSw
732 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
733 * @return true if it succeeds in populating rules
734 */ // refactor
735 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
736 DeviceId destSw1,
737 DeviceId destSw2,
738 Map<DeviceId, Set<DeviceId>> nextHops,
739 Set<IpPrefix> subnets) {
740 boolean result;
741 // If both target switch and dest switch are edge routers, then set IP
742 // rule for both subnet and router IP.
743 boolean targetIsEdge;
744 boolean dest1IsEdge;
745 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
746 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
747
748 try {
749 targetIsEdge = config.isEdgeDevice(targetSw);
750 dest1IsEdge = config.isEdgeDevice(destSw1);
751 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
752 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
753 if (destSw2 != null) {
754 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
755 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
756 }
757 } catch (DeviceConfigNotFoundException e) {
758 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700759 return false;
760 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700761
762 if (targetIsEdge && dest1IsEdge) {
763 subnets = (subnets != null && !subnets.isEmpty())
764 ? Sets.newHashSet(subnets)
765 : Sets.newHashSet(config.getSubnets(destSw1));
766 // XXX - Rethink this
767 /*subnets.add(dest1RouterIpv4.toIpPrefix());
768 if (dest1RouterIpv6 != null) {
769 subnets.add(dest1RouterIpv6.toIpPrefix());
770 }
771 if (destSw2 != null && dest2RouterIpv4 != null) {
772 subnets.add(dest2RouterIpv4.toIpPrefix());
773 if (dest2RouterIpv6 != null) {
774 subnets.add(dest2RouterIpv6.toIpPrefix());
775 }
776 }*/
777 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
778 + "for subnets {}", targetSw, destSw1,
779 (destSw2 != null) ? ("& " + destSw2) : "",
780 subnets);
781 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
782 destSw1, destSw2,
783 nextHops);
784 if (!result) {
785 return false;
786 }
787 /* XXX rethink this
788 IpPrefix routerIpPrefix = destRouterIpv4.toIpPrefix();
789 log.debug("* populateEcmpRoutingRulePartial in device {} towards {} "
790 + "for router IP {}", targetSw, destSw, routerIpPrefix);
791 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
792 destSw, nextHops);
793 if (!result) {
794 return false;
795 }
796 // If present we deal with IPv6 loopback.
797 if (destRouterIpv6 != null) {
798 routerIpPrefix = destRouterIpv6.toIpPrefix();
799 log.debug("* populateEcmpRoutingRulePartial in device {} towards {}"
800 + " for v6 router IP {}", targetSw, destSw, routerIpPrefix);
801 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
802 destSw, nextHops);
803 if (!result) {
804 return false;
805 }
806 }*/
Saurav Dasc88d4662017-05-15 15:34:25 -0700807 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700808
809 if (!targetIsEdge && dest1IsEdge) {
810 // MPLS rules in all non-edge target devices. These rules are for
811 // individual destinations, even if the dsts are part of edge-pairs.
812 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
813 + "all MPLS rules", targetSw, destSw1);
814 result = rulePopulator.populateMplsRule(targetSw, destSw1,
815 nextHops.get(destSw1),
816 dest1RouterIpv4);
817 if (!result) {
818 return false;
819 }
820 if (dest1RouterIpv6 != null) {
821 result = rulePopulator.populateMplsRule(targetSw, destSw1,
822 nextHops.get(destSw1),
823 dest1RouterIpv6);
824 if (!result) {
825 return false;
826 }
827 }
828 }
829
830 // To save on ECMP groups
831 // avoid MPLS rules in non-edge-devices to non-edge-devices
832 // avoid MPLS transit rules in edge-devices
833 // avoid loopback IP rules in edge-devices to non-edge-devices
834 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700835 }
836
837 /**
838 * Processes a set a route-path changes by editing hash groups.
839 *
840 * @param routeChanges a set of route-path changes, where each route-path is
841 * a list with its first element the src-switch of the path
842 * and the second element the dst-switch of the path.
843 * @param linkOrSwitchFailed true if the route changes are for a failed
844 * switch or linkDown event
845 * @param failedSwitch the switchId if the route changes are for a failed switch,
846 * otherwise null
847 */
848 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
849 boolean linkOrSwitchFailed,
850 DeviceId failedSwitch) {
Saurav Das041bb782017-08-14 16:44:43 -0700851 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
852 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700853 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das041bb782017-08-14 16:44:43 -0700854 if (route.size() == 1) {
855 // route-path changes are from everyone else to this switch
856 DeviceId dstSw = route.get(0);
857 srManager.deviceService.getAvailableDevices().forEach(sw -> {
858 if (!sw.id().equals(dstSw)) {
859 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
860 }
861 });
862 } else {
863 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700864 }
Saurav Das041bb782017-08-14 16:44:43 -0700865 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700866
Saurav Das041bb782017-08-14 16:44:43 -0700867 for (ArrayList<DeviceId> route : changedRoutes) {
868 DeviceId targetSw = route.get(0);
869 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700870 if (linkOrSwitchFailed) {
Saurav Das041bb782017-08-14 16:44:43 -0700871 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700872 // it's possible that we cannot fix hash groups for a route
873 // if the target switch has failed. Nevertheless the ecmp graph
874 // for the impacted switch must still be updated.
Saurav Das041bb782017-08-14 16:44:43 -0700875 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700876 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
877 currentEcmpSpgMap.remove(targetSw);
Saurav Das041bb782017-08-14 16:44:43 -0700878 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -0700879 + "target:{}", dstSw, targetSw);
Saurav Das041bb782017-08-14 16:44:43 -0700880 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -0700881 }
882 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -0700883 if (success) {
884 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das041bb782017-08-14 16:44:43 -0700885 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
886 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown",
887 dstSw, targetSw);
888 }
889 } else {
890 //linkup of seen before link
891 boolean success = fixHashGroupsForRoute(route, false);
892 if (success) {
893 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
894 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
895 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -0700896 targetSw, dstSw);
897 }
898 }
899 }
900 }
901
902 /**
903 * Edits hash groups in the src-switch (targetSw) of a route-path by
904 * calling the groupHandler to either add or remove buckets in an existing
905 * hash group.
906 *
907 * @param route a single list representing a route-path where the first element
908 * is the src-switch (targetSw) of the route-path and the
909 * second element is the dst-switch
910 * @param revoke true if buckets in the hash-groups need to be removed;
911 * false if buckets in the hash-groups need to be added
912 * @return true if the hash group editing is successful
913 */
914 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
915 boolean revoke) {
916 DeviceId targetSw = route.get(0);
917 if (route.size() < 2) {
918 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
919 return false;
920 }
921 DeviceId destSw = route.get(1);
Saurav Das041bb782017-08-14 16:44:43 -0700922 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -0700923 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -0700924 // figure out the new next hops at the targetSw towards the destSw
Saurav Das041bb782017-08-14 16:44:43 -0700925 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -0700926 // call group handler to change hash group at targetSw
927 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
928 if (grpHandler == null) {
929 log.warn("Cannot find grouphandler for dev:{} .. aborting"
930 + " {} hash group buckets for route:{} ", targetSw,
931 (revoke) ? "revoke" : "repopulate", route);
932 return false;
933 }
934 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
935 (revoke) ? "revoke" : "repopulating",
936 targetSw, destSw, nextHops);
937 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
938 destSw, true)
939 : grpHandler.fixHashGroups(targetSw, nextHops,
940 destSw, false);
941 }
942
943 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700944 * Start the flow rule population process if it was never started. The
945 * process finishes successfully when all flow rules are set and stops with
946 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -0700947 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700948 public void startPopulationProcess() {
949 statusLock.lock();
950 try {
951 if (populationStatus == Status.IDLE
952 || populationStatus == Status.SUCCEEDED
953 || populationStatus == Status.ABORTED) {
954 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -0700955 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -0700956 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
957 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700958 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700959 } finally {
960 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700961 }
sangho20eff1d2015-04-13 15:15:58 -0700962 }
963
Saurav Dasb5c236e2016-06-07 10:08:06 -0700964 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700965 * Revoke rules of given subnet in all edge switches.
966 *
967 * @param subnets subnet being removed
968 * @return true if succeed
969 */
970 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
971 statusLock.lock();
972 try {
973 return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
974 } finally {
975 statusLock.unlock();
976 }
977 }
978
979 /**
Charles Chan23686832017-08-23 14:46:43 -0700980 * Populates IP rules for a route that has direct connection to the switch
981 * if the current instance is the master of the switch.
982 *
983 * @param deviceId device ID of the device that next hop attaches to
984 * @param prefix IP prefix of the route
985 * @param hostMac MAC address of the next hop
986 * @param hostVlanId Vlan ID of the nexthop
987 * @param outPort port where the next hop attaches to
988 */
989 void populateRoute(DeviceId deviceId, IpPrefix prefix,
990 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
991 if (srManager.mastershipService.isLocalMaster(deviceId)) {
992 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
993 }
994 }
995
996 /**
997 * Removes IP rules for a route when the next hop is gone.
998 * if the current instance is the master of the switch.
999 *
1000 * @param deviceId device ID of the device that next hop attaches to
1001 * @param prefix IP prefix of the route
1002 * @param hostMac MAC address of the next hop
1003 * @param hostVlanId Vlan ID of the nexthop
1004 * @param outPort port that next hop attaches to
1005 */
1006 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1007 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
1008 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1009 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1010 }
1011 }
1012
1013 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001014 * Remove ECMP graph entry for the given device. Typically called when
1015 * device is no longer available.
1016 *
1017 * @param deviceId the device for which graphs need to be purged
1018 */
1019 protected void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das041bb782017-08-14 16:44:43 -07001020 currentEcmpSpgMap.remove(deviceId); // XXX reconsider
Saurav Das7bcbe702017-06-13 15:35:54 -07001021 if (updatedEcmpSpgMap != null) {
1022 updatedEcmpSpgMap.remove(deviceId);
1023 }
1024 }
1025
1026 //////////////////////////////////////
1027 // Routing helper methods and classes
1028 //////////////////////////////////////
1029
1030 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001031 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001032 * previous ecmp shortest-path graph exists for a switch in order to compute
1033 * affected routes. If such a graph does not exist, the method returns null.
1034 *
1035 * @param linkFail the failed link
1036 * @return the set of affected routes which may be empty if no routes were
1037 * affected, or null if no previous ecmp spg was found for comparison
1038 */
sangho20eff1d2015-04-13 15:15:58 -07001039 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001040 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1041
1042 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001043 log.debug("Computing the impacted routes for device {} due to link fail",
1044 sw.id());
Saurav Das041bb782017-08-14 16:44:43 -07001045 DeviceId retId = shouldHandleRouting(sw.id());
1046 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001047 continue;
1048 }
Saurav Das041bb782017-08-14 16:44:43 -07001049 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1050 for (DeviceId rootSw : devicesToProcess) {
1051 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1052 if (ecmpSpg == null) {
1053 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1054 + " rerouting and opting for full-reroute", rootSw);
1055 return null;
1056 }
1057 if (log.isDebugEnabled()) {
1058 log.debug("Root switch: {}", rootSw);
1059 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1060 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1061 }
1062 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1063 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1064 // figure out if the broken link affected any route-paths in this graph
1065 for (Integer itrIdx : switchVia.keySet()) {
1066 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1067 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1068 switchVia.get(itrIdx);
1069 for (DeviceId targetSw : swViaMap.keySet()) {
1070 log.trace("TargetSwitch {} --> RootSwitch {}",
1071 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001072 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1073 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001074 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001075 }
Saurav Das041bb782017-08-14 16:44:43 -07001076 Set<ArrayList<DeviceId>> subLinks =
1077 computeLinks(targetSw, rootSw, swViaMap);
1078 for (ArrayList<DeviceId> alink: subLinks) {
1079 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1080 alink.get(1).equals(linkFail.dst().deviceId()))
1081 ||
1082 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1083 alink.get(1).equals(linkFail.src().deviceId()))) {
1084 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1085 ArrayList<DeviceId> aRoute = new ArrayList<>();
1086 aRoute.add(targetSw); // switch with rules to populate
1087 aRoute.add(rootSw); // towards this destination
1088 routes.add(aRoute);
1089 break;
1090 }
sangho20eff1d2015-04-13 15:15:58 -07001091 }
1092 }
1093 }
Saurav Das041bb782017-08-14 16:44:43 -07001094
sangho20eff1d2015-04-13 15:15:58 -07001095 }
sangho45b009c2015-05-07 13:30:57 -07001096
sangho20eff1d2015-04-13 15:15:58 -07001097 }
sangho20eff1d2015-04-13 15:15:58 -07001098 return routes;
1099 }
1100
Saurav Das4e3224f2016-11-29 14:27:25 -08001101 /**
1102 * Computes set of affected routes due to new links or failed switches.
1103 *
1104 * @return the set of affected routes which may be empty if no routes were
1105 * affected
1106 */
sangho20eff1d2015-04-13 15:15:58 -07001107 private Set<ArrayList<DeviceId>> computeRouteChange() {
Saurav Das7bcbe702017-06-13 15:35:54 -07001108 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001109 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001110
1111 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001112 log.debug("Computing the impacted routes for device {}", sw.id());
1113 DeviceId retId = shouldHandleRouting(sw.id());
1114 if (retId == null) {
sangho20eff1d2015-04-13 15:15:58 -07001115 continue;
1116 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001117 Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
1118 for (DeviceId rootSw : devicesToProcess) {
1119 if (log.isTraceEnabled()) {
1120 log.trace("Device links for dev: {}", rootSw);
1121 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1122 log.trace("{} -> {} ", link.src().deviceId(),
1123 link.dst().deviceId());
1124 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001125 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001126 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1127 if (currEcmpSpg == null) {
1128 log.debug("No existing ECMP graph for device {}.. adding self as "
1129 + "changed route", rootSw);
1130 changedRtBldr.add(Lists.newArrayList(rootSw));
1131 continue;
1132 }
1133 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
1134 if (log.isDebugEnabled()) {
1135 log.debug("Root switch: {}", rootSw);
1136 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1137 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1138 }
1139 // first use the updated/new map to compare to current/existing map
1140 // as new links may have come up
1141 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1142 // then use the current/existing map to compare to updated/new map
1143 // as switch may have been removed
1144 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001145 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001146 }
sangho20eff1d2015-04-13 15:15:58 -07001147
Saurav Das7bcbe702017-06-13 15:35:54 -07001148 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001149 for (ArrayList<DeviceId> route: changedRoutes) {
1150 log.debug("Route changes Target -> Root");
1151 if (route.size() == 1) {
1152 log.debug(" : all -> {}", route.get(0));
1153 } else {
1154 log.debug(" : {} -> {}", route.get(0), route.get(1));
1155 }
1156 }
1157 return changedRoutes;
1158 }
1159
1160 /**
1161 * For the root switch, searches all the target nodes reachable in the base
1162 * graph, and compares paths to the ones in the comp graph.
1163 *
1164 * @param base the graph that is indexed for all reachable target nodes
1165 * from the root node
1166 * @param comp the graph that the base graph is compared to
1167 * @param rootSw both ecmp graphs are calculated for the root node
1168 * @return all the routes that have changed in the base graph
1169 */
1170 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1171 EcmpShortestPathGraph comp,
1172 DeviceId rootSw) {
1173 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1174 ImmutableSet.builder();
1175 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1176 base.getAllLearnedSwitchesAndVia();
1177 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1178 comp.getAllLearnedSwitchesAndVia();
1179 for (Integer itrIdx : baseMap.keySet()) {
1180 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1181 baseMap.get(itrIdx);
1182 for (DeviceId targetSw : baseViaMap.keySet()) {
1183 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1184 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1185 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001186 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001187 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001188 route.add(targetSw); // switch with rules to populate
1189 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001190 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001191 }
1192 }
sangho45b009c2015-05-07 13:30:57 -07001193 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001194 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001195 }
1196
Saurav Das7bcbe702017-06-13 15:35:54 -07001197 /**
1198 * Returns the ECMP paths traversed to reach the target switch.
1199 *
1200 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1201 * @param targetSw the switch to reach from the root switch
1202 * @return the nodes traversed on ECMP paths to the target switch
1203 */
sangho20eff1d2015-04-13 15:15:58 -07001204 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001205 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001206 for (Integer itrIdx : switchVia.keySet()) {
1207 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1208 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001209 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001210 continue;
1211 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001212 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001213 }
1214 }
1215
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001216 return null;
sangho20eff1d2015-04-13 15:15:58 -07001217 }
1218
Saurav Das7bcbe702017-06-13 15:35:54 -07001219 /**
1220 * Utility method to break down a path from src to dst device into a collection
1221 * of links.
1222 *
1223 * @param src src device of the path
1224 * @param dst dst device of the path
1225 * @param viaMap path taken from src to dst device
1226 * @return collection of links in the path
1227 */
sangho20eff1d2015-04-13 15:15:58 -07001228 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1229 DeviceId dst,
1230 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1231 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1232 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1233 DeviceId linkSrc = src;
1234 DeviceId linkDst = dst;
1235 for (DeviceId viaDevice: via) {
1236 ArrayList<DeviceId> link = new ArrayList<>();
1237 linkDst = viaDevice;
1238 link.add(linkSrc);
1239 link.add(linkDst);
1240 subLinks.add(link);
1241 linkSrc = viaDevice;
1242 }
1243 ArrayList<DeviceId> link = new ArrayList<>();
1244 link.add(linkSrc);
1245 link.add(dst);
1246 subLinks.add(link);
1247 }
1248
1249 return subLinks;
1250 }
1251
Charles Chan93e71ba2016-04-29 14:38:22 -07001252 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001253 * Determines whether this controller instance should handle routing for the
1254 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
1255 * Returns null if this instance should not handle routing for given {@code deviceId}.
1256 * Otherwise the returned value could be the given deviceId itself, or the
1257 * deviceId for the paired edge device. In the latter case, this instance
1258 * should handle routing for both the given device and the paired device.
Charles Chan93e71ba2016-04-29 14:38:22 -07001259 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001260 * @param deviceId device identifier to consider for routing
1261 * @return null or deviceId which could be the same as the given deviceId
1262 * or the deviceId of a paired edge device
Charles Chan93e71ba2016-04-29 14:38:22 -07001263 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001264 private DeviceId shouldHandleRouting(DeviceId deviceId) {
1265 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1266 log.debug("Not master for dev:{} .. skipping routing, may get handled "
1267 + "elsewhere as part of paired devices", deviceId);
1268 return null;
1269 }
1270 NodeId myNode = srManager.mastershipService.getMasterFor(deviceId);
1271 DeviceId pairDev = getPairDev(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001272
Saurav Das7bcbe702017-06-13 15:35:54 -07001273 if (pairDev != null) {
1274 if (!srManager.deviceService.isAvailable(pairDev)) {
1275 log.warn("pairedDev {} not available .. routing this dev:{} "
1276 + "without mastership check",
1277 pairDev, deviceId);
1278 return pairDev; // handle both temporarily
1279 }
1280 NodeId pairMasterNode = srManager.mastershipService.getMasterFor(pairDev);
1281 if (myNode.compareTo(pairMasterNode) <= 0) {
1282 log.debug("Handling routing for both dev:{} pair-dev:{}; myNode: {}"
1283 + " pairMaster:{} compare:{}", deviceId, pairDev,
1284 myNode, pairMasterNode,
1285 myNode.compareTo(pairMasterNode));
1286 return pairDev; // handle both
1287 } else {
1288 log.debug("PairDev node: {} should handle routing for dev:{} and "
1289 + "pair-dev:{}", pairMasterNode, deviceId, pairDev);
1290 return null; // handle neither
sanghob35a6192015-04-01 13:05:26 -07001291 }
1292 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001293 return deviceId; // not paired, just handle given device
sanghob35a6192015-04-01 13:05:26 -07001294 }
1295
Charles Chan93e71ba2016-04-29 14:38:22 -07001296 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001297 * Returns the configured paired DeviceId for the given Device, or null
1298 * if no such paired device has been configured.
Charles Chan93e71ba2016-04-29 14:38:22 -07001299 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001300 * @param deviceId
1301 * @return configured pair deviceId or null
Charles Chan93e71ba2016-04-29 14:38:22 -07001302 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001303 private DeviceId getPairDev(DeviceId deviceId) {
1304 DeviceId pairDev;
Charles Chan0b4e6182015-11-03 10:42:14 -08001305 try {
Saurav Das7bcbe702017-06-13 15:35:54 -07001306 pairDev = srManager.deviceConfiguration.getPairDeviceId(deviceId);
Charles Chan0b4e6182015-11-03 10:42:14 -08001307 } catch (DeviceConfigNotFoundException e) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001308 log.warn(e.getMessage() + " .. cannot continue routing for dev: {}");
1309 return null;
Charles Chan0b4e6182015-11-03 10:42:14 -08001310 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001311 return pairDev;
sanghob35a6192015-04-01 13:05:26 -07001312 }
1313
1314 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001315 * Returns the set of deviceIds which are the next hops from the targetSw
1316 * to the dstSw according to the latest ECMP spg.
1317 *
1318 * @param targetSw the switch for which the next-hops are desired
1319 * @param dstSw the switch to which the next-hops lead to from the targetSw
1320 * @return set of next hop deviceIds, could be empty if no next hops are found
1321 */
1322 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1323 boolean targetIsEdge = false;
1324 try {
1325 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1326 } catch (DeviceConfigNotFoundException e) {
1327 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1328 + "continuing to getNextHops", targetSw);
1329 }
1330
1331 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1332 if (ecmpSpg == null) {
1333 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1334 return ImmutableSet.of();
1335 }
1336 HashMap<Integer,
1337 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1338 ecmpSpg.getAllLearnedSwitchesAndVia();
1339 for (Integer itrIdx : switchVia.keySet()) {
1340 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1341 switchVia.get(itrIdx);
1342 for (DeviceId target : swViaMap.keySet()) {
1343 if (!target.equals(targetSw)) {
1344 continue;
1345 }
1346 if (!targetIsEdge && itrIdx > 1) {
1347 // optimization for spines to not use other leaves to get
1348 // to a leaf to avoid loops
1349 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1350 + " --> dstSw:{}", itrIdx, targetSw, dstSw);
1351 break;
1352 }
1353 Set<DeviceId> nextHops = new HashSet<>();
1354 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1355 if (via.isEmpty()) {
1356 // the dstSw is the next-hop from the targetSw
1357 nextHops.add(dstSw);
1358 } else {
1359 // first elem is next-hop in each ECMP path
1360 nextHops.add(via.get(0));
1361 }
1362 }
1363 return nextHops;
1364 }
1365 }
1366 return ImmutableSet.of(); //no next-hops found
1367 }
1368
1369 /**
1370 * Represents two devices that are paired by configuration. An EdgePair for
1371 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1372 */
1373 protected final class EdgePair {
1374 DeviceId dev1;
1375 DeviceId dev2;
1376
1377 EdgePair(DeviceId dev1, DeviceId dev2) {
1378 this.dev1 = dev1;
1379 this.dev2 = dev2;
1380 }
1381
1382 boolean includes(DeviceId dev) {
1383 return dev1.equals(dev) || dev2.equals(dev);
1384 }
1385
1386 @Override
1387 public boolean equals(Object o) {
1388 if (this == o) {
1389 return true;
1390 }
1391 if (!(o instanceof EdgePair)) {
1392 return false;
1393 }
1394 EdgePair that = (EdgePair) o;
1395 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1396 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1397 }
1398
1399 @Override
1400 public int hashCode() {
1401 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1402 return Objects.hash(dev1, dev2);
1403 } else {
1404 return Objects.hash(dev2, dev1);
1405 }
1406 }
1407
1408 @Override
1409 public String toString() {
1410 return toStringHelper(this)
1411 .add("Dev1", dev1)
1412 .add("Dev2", dev2)
1413 .toString();
1414 }
1415 }
1416
Saurav Das041bb782017-08-14 16:44:43 -07001417 /**
1418 * Updates the currentEcmpSpgGraph for all devices.
1419 */
1420 private void updateEcmpSpgMaps() {
1421 for (Device sw : srManager.deviceService.getDevices()) {
1422 EcmpShortestPathGraph ecmpSpgUpdated =
1423 new EcmpShortestPathGraph(sw.id(), srManager);
1424 currentEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
1425 }
1426 }
1427
1428 /**
1429 * Ensures routing is stable before updating all ECMP SPG graphs.
1430 *
1431 * TODO: CORD-1843 will ensure maps are updated faster, potentially while
1432 * topology/routing is still unstable
1433 */
1434 private final class UpdateMaps implements Runnable {
1435 @Override
1436 public void run() {
1437 if (isRoutingStable()) {
1438 updateEcmpSpgMaps();
1439 } else {
1440 executorService.schedule(new UpdateMaps(), UPDATE_INTERVAL,
1441 TimeUnit.SECONDS);
1442 }
1443 }
1444 }
1445
Saurav Das7bcbe702017-06-13 15:35:54 -07001446 //////////////////////////////////////
1447 // Filtering rule creation
1448 //////////////////////////////////////
1449
1450 /**
Saurav Das018605f2017-02-18 14:05:44 -08001451 * Populates filtering rules for port, and punting rules
1452 * for gateway IPs, loopback IPs and arp/ndp traffic.
1453 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001454 *
1455 * @param deviceId Switch ID to set the rules
1456 */
Saurav Das822c4e22015-10-23 10:51:11 -07001457 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001458 // Although device is added, sometimes device store does not have the
1459 // ports for this device yet. It results in missing filtering rules in the
1460 // switch. We will attempt it a few times. If it still does not work,
1461 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001462 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001463 if (firstRun == null) {
1464 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001465 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001466 executorService.schedule(new RetryFilters(deviceId, firstRun),
1467 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001468 }
1469
1470 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001471 * Utility class used to temporarily store information about the ports on a
1472 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001473 */
1474 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001475 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001476
Saurav Das018605f2017-02-18 14:05:44 -08001477 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001478 int filteredPorts) {
1479 this.disabledPorts = disabledPorts;
1480 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001481 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001482 }
1483
1484 @Override
1485 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001486 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001487 }
1488
1489 @Override
1490 public boolean equals(Object obj) {
1491 if (this == obj) {
1492 return true;
1493 }
1494 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1495 return false;
1496 }
1497 PortFilterInfo other = (PortFilterInfo) obj;
1498 return ((disabledPorts == other.disabledPorts) &&
1499 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001500 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001501 }
1502
1503 @Override
1504 public String toString() {
1505 MoreObjects.ToStringHelper helper = toStringHelper(this)
1506 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001507 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001508 .add("filteredPorts", filteredPorts);
1509 return helper.toString();
1510 }
1511 }
1512
1513 /**
1514 * RetryFilters populates filtering objectives for a device and keeps retrying
1515 * till the number of ports filtered are constant for a predefined number
1516 * of attempts.
1517 */
1518 protected final class RetryFilters implements Runnable {
1519 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1520 DeviceId devId;
1521 int counter;
1522 PortFilterInfo prevRun;
1523
1524 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001525 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001526 prevRun = previousRun;
1527 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001528 }
1529
1530 @Override
1531 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001532 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001533 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001534 boolean sameResult = prevRun.equals(thisRun);
1535 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1536 thisRun, sameResult);
1537 if (thisRun == null || !sameResult || (sameResult && --constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001538 // exponentially increasing intervals for retries
1539 executorService.schedule(this,
1540 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1541 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001542 if (!sameResult) {
1543 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1544 }
Saurav Das59232cf2016-04-27 18:35:50 -07001545 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001546 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001547 }
Saurav Das59232cf2016-04-27 18:35:50 -07001548 }
1549
sanghob35a6192015-04-01 13:05:26 -07001550}