blob: 9b271646443ef4d57be6fa464b78017296afc73e [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;
sangho666cd6d2015-04-14 16:27:13 -070025import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080026import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070027import org.onlab.packet.IpPrefix;
Charles Chan93e71ba2016-04-29 14:38:22 -070028import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070029import org.onosproject.net.Device;
30import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070031import org.onosproject.net.Link;
Charles Chan0b4e6182015-11-03 10:42:14 -080032import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
33import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070034import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
sanghob35a6192015-04-01 13:05:26 -070035import org.slf4j.Logger;
36import org.slf4j.LoggerFactory;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.HashSet;
Saurav Dasd2fded02016-12-02 15:43:47 -080041import java.util.Objects;
sanghob35a6192015-04-01 13:05:26 -070042import java.util.Set;
Saurav Das59232cf2016-04-27 18:35:50 -070043import java.util.concurrent.ScheduledExecutorService;
44import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090045import java.util.concurrent.locks.Lock;
46import java.util.concurrent.locks.ReentrantLock;
sanghob35a6192015-04-01 13:05:26 -070047
Saurav Dasd2fded02016-12-02 15:43:47 -080048import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventree0ae7a32016-11-23 09:57:42 -080049import static com.google.common.base.Preconditions.checkNotNull;
50import static java.util.concurrent.Executors.newScheduledThreadPool;
51import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070052
Charles Chane849c192016-01-11 18:28:54 -080053/**
54 * Default routing handler that is responsible for route computing and
55 * routing rule population.
56 */
sanghob35a6192015-04-01 13:05:26 -070057public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080058 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
59 private static final int RETRY_INTERVAL_MS = 250;
60 private static final int RETRY_INTERVAL_SCALE = 1;
Charles Chan93e71ba2016-04-29 14:38:22 -070061 private static final String ECMPSPG_MISSING = "ECMP shortest path graph not found";
62 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070063
64 private SegmentRoutingManager srManager;
65 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053066 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
67 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070068 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090069 private final Lock statusLock = new ReentrantLock();
70 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070071 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080072 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
sanghob35a6192015-04-01 13:05:26 -070073
74 /**
75 * Represents the default routing population status.
76 */
77 public enum Status {
78 // population process is not started yet.
79 IDLE,
80
81 // population process started.
82 STARTED,
83
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -070084 // population process was aborted due to errors, mostly for groups not
85 // found.
sanghob35a6192015-04-01 13:05:26 -070086 ABORTED,
87
88 // population process was finished successfully.
89 SUCCEEDED
90 }
91
92 /**
93 * Creates a DefaultRoutingHandler object.
94 *
95 * @param srManager SegmentRoutingManager object
96 */
97 public DefaultRoutingHandler(SegmentRoutingManager srManager) {
98 this.srManager = srManager;
99 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700100 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700101 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700102 this.currentEcmpSpgMap = Maps.newHashMap();
sanghob35a6192015-04-01 13:05:26 -0700103 }
104
105 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700106 * Returns an immutable copy of the current ECMP shortest-path graph as
107 * computed by this controller instance.
108 *
109 * @return the current ECMP graph
110 */
111 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
112 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
113 currentEcmpSpgMap.entrySet().forEach(entry -> {
114 if (entry.getValue() != null) {
115 builder.put(entry.getKey(), entry.getValue());
116 }
117 });
118 return builder.build();
119 }
120
121 /**
sanghob35a6192015-04-01 13:05:26 -0700122 * Populates all routing rules to all connected routers, including default
123 * routing rules, adjacency rules, and policy rules if any.
sanghob35a6192015-04-01 13:05:26 -0700124 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700125 public void populateAllRoutingRules() {
sanghob35a6192015-04-01 13:05:26 -0700126
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900127 statusLock.lock();
128 try {
129 populationStatus = Status.STARTED;
130 rulePopulator.resetCounter();
Saurav Dasa07f2032015-10-19 14:37:36 -0700131 log.info("Starting to populate segment-routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900132 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700133
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900134 for (Device sw : srManager.deviceService.getDevices()) {
Charles Chanc42e84e2015-10-20 16:24:19 -0700135 if (!srManager.mastershipService.isLocalMaster(sw.id())) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700136 log.debug("populateAllRoutingRules: skipping device {}..."
137 + "we are not master", sw.id());
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900138 continue;
139 }
140
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530141 EcmpShortestPathGraph ecmpSpg = new EcmpShortestPathGraph(sw.id(), srManager);
Charles Chan93e71ba2016-04-29 14:38:22 -0700142 if (!populateEcmpRoutingRules(sw.id(), ecmpSpg, ImmutableSet.of())) {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900143 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
144 populationStatus = Status.ABORTED;
145 log.debug("Abort routing rule population");
Saurav Dasc88d4662017-05-15 15:34:25 -0700146 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900147 }
148 currentEcmpSpgMap.put(sw.id(), ecmpSpg);
Saurav Dasc88d4662017-05-15 15:34:25 -0700149 log.debug("Updating ECMPspg for sw:{}", sw.id());
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900150
151 // TODO: Set adjacency routing rule for all switches
sanghob35a6192015-04-01 13:05:26 -0700152 }
153
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900154 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
155 populationStatus = Status.SUCCEEDED;
Saurav Dasa07f2032015-10-19 14:37:36 -0700156 log.info("Completed routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900157 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700158 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900159 } finally {
160 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700161 }
sanghob35a6192015-04-01 13:05:26 -0700162 }
163
sangho20eff1d2015-04-13 15:15:58 -0700164 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700165 * Populates the routing rules or makes hash group changes according to the
166 * route-path changes due to link failure, switch failure or link up. This
167 * method should only be called for one of these three possible event-types.
168 * Note that when a switch goes away, all of its links fail as well,
169 * but this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700170 *
Saurav Dasc88d4662017-05-15 15:34:25 -0700171 * @param linkDown the single failed link, or null for other conditions
172 * such as link-up or a removed switch
173 * @param linkUp the single link up, or null for other conditions such as
174 * link-down or a removed switch
175 * @param switchDown the removed switch, or null for other conditions such as
176 * link-down or link-up
sangho20eff1d2015-04-13 15:15:58 -0700177 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700178 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
179 Link linkUp,
180 DeviceId switchDown) {
181 if ((linkDown != null && (linkUp != null || switchDown != null)) ||
182 (linkUp != null && (linkDown != null || switchDown != null)) ||
183 (switchDown != null && (linkUp != null || linkDown != null))) {
184 log.warn("Only one event can be handled for link status change .. aborting");
185 return;
186 }
sangho20eff1d2015-04-13 15:15:58 -0700187
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900188 statusLock.lock();
189 try {
sangho20eff1d2015-04-13 15:15:58 -0700190
191 if (populationStatus == Status.STARTED) {
sangho52abe3a2015-05-05 14:13:34 -0700192 log.warn("Previous rule population is not finished.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700193 return;
sangho20eff1d2015-04-13 15:15:58 -0700194 }
195
sangho45b009c2015-05-07 13:30:57 -0700196 // Take the snapshots of the links
197 updatedEcmpSpgMap = new HashMap<>();
198 for (Device sw : srManager.deviceService.getDevices()) {
Charles Chanc42e84e2015-10-20 16:24:19 -0700199 if (!srManager.mastershipService.isLocalMaster(sw.id())) {
sangho45b009c2015-05-07 13:30:57 -0700200 continue;
201 }
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530202 EcmpShortestPathGraph ecmpSpgUpdated =
203 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700204 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
205 }
206
sangho52abe3a2015-05-05 14:13:34 -0700207 log.info("Starts rule population from link change");
208
sangho20eff1d2015-04-13 15:15:58 -0700209 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700210 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700211 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700212 populationStatus = Status.STARTED;
Saurav Das4e3224f2016-11-29 14:27:25 -0800213 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700214 if (linkDown == null) {
215 // either a linkUp or a switchDown - compute all route changes by
216 // comparing all routes of existing ECMP SPG to new ECMP SPG
sangho20eff1d2015-04-13 15:15:58 -0700217 routeChanges = computeRouteChange();
Saurav Dasc88d4662017-05-15 15:34:25 -0700218
219 if (routeChanges != null) {
220 // deal with linkUp of a seen-before link
221 if (linkUp != null && srManager.isSeenLink(linkUp)) {
222 if (!isBidirectional(linkUp)) {
223 log.warn("Not a bidirectional link yet .. not "
224 + "processing link {}", linkUp);
225 srManager.updateSeenLink(linkUp, true);
226 populationStatus = Status.ABORTED;
227 return;
228 }
229 // link previously seen before
230 // do hash-bucket changes instead of a re-route
231 processHashGroupChange(routeChanges, false, null);
232 // clear out routesChanges so a re-route is not attempted
233 routeChanges = ImmutableSet.of();
234 }
235
236 //deal with switchDown
237 if (switchDown != null) {
238 processHashGroupChange(routeChanges, true, switchDown);
239 // clear out routesChanges so a re-route is not attempted
240 routeChanges = ImmutableSet.of();
241 }
242
243 // for a linkUp of a never-seen-before link
244 // let it fall through to a reroute of the routeChanges
245
246 }
247
248 // now that we are past the check for a previously seen link
249 // it is safe to update the store for the linkUp
250 if (linkUp != null) {
251 srManager.updateSeenLink(linkUp, true);
252 }
253
sangho20eff1d2015-04-13 15:15:58 -0700254 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700255 // link has gone down
256 // Compare existing ECMP SPG only with the link that went down
257 routeChanges = computeDamagedRoutes(linkDown);
258 if (routeChanges != null) {
259 processHashGroupChange(routeChanges, true, null);
260 // clear out routesChanges so a re-route is not attempted
261 routeChanges = ImmutableSet.of();
262 }
sangho20eff1d2015-04-13 15:15:58 -0700263 }
264
Saurav Das4e3224f2016-11-29 14:27:25 -0800265 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700266 if (routeChanges == null) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700267 log.info("Optimized routing failed... doing full re-route");
268 populateAllRoutingRules();
269 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700270 }
271
sangho20eff1d2015-04-13 15:15:58 -0700272 if (routeChanges.isEmpty()) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700273 log.info("No re-route attempted for the link status change");
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700274 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700275 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700276 return;
sangho20eff1d2015-04-13 15:15:58 -0700277 }
278
Saurav Dasc88d4662017-05-15 15:34:25 -0700279 // reroute of routeChanges
sangho20eff1d2015-04-13 15:15:58 -0700280 if (repopulateRoutingRulesForRoutes(routeChanges)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700281 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700282 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700283 log.info("Completed repopulation of rules. # of rules populated : {}",
sangho20eff1d2015-04-13 15:15:58 -0700284 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700285 return;
sangho20eff1d2015-04-13 15:15:58 -0700286 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700287 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700288 populationStatus = Status.ABORTED;
289 log.warn("Failed to repopulate the rules.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700290 return;
sangho20eff1d2015-04-13 15:15:58 -0700291 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900292 } finally {
293 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700294 }
295 }
296
Saurav Dasc88d4662017-05-15 15:34:25 -0700297 /**
298 * Returns true if the link being queried is a bidirectional link. A bidi
299 * link is defined as a link, whose reverse link - ie. the link in the reverse
300 * direction - has been seen-before and is up.
301 *
302 * @param link the infrastructure link being queried
303 * @return true if another unidirectional link exists in the reverse direction,
304 * has been seen-before and is up
305 */
306 private boolean isBidirectional(Link link) {
307 Link reverseLink = srManager.linkService.getLink(link.dst(), link.src());
308 if (reverseLink == null) {
309 return false;
310 }
311 Boolean result = srManager.isSeenLinkUp(reverseLink);
312 if (result == null) {
313 return false;
314 }
315 return result.booleanValue();
316 }
317
318 /**
319 * Processes a set a route-path changes by editing hash groups.
320 *
321 * @param routeChanges a set of route-path changes, where each route-path is
322 * a list with its first element the src-switch of the path
323 * and the second element the dst-switch of the path.
324 * @param linkOrSwitchFailed true if the route changes are for a failed
325 * switch or linkDown event
326 * @param failedSwitch the switchId if the route changes are for a failed switch,
327 * otherwise null
328 */
329 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
330 boolean linkOrSwitchFailed,
331 DeviceId failedSwitch) {
332 for (ArrayList<DeviceId> route : routeChanges) {
333 DeviceId targetSw = route.get(0);
334 boolean success;
335 DeviceId dstSw = null;
336 if (route.size() > 1) {
337 dstSw = route.get(1);
338 }
339
340 if (linkOrSwitchFailed) {
341 success = fixHashGroupsForRoute(route, true);
342 // it's possible that we cannot fix hash groups for a route
343 // if the target switch has failed. Nevertheless the ecmp graph
344 // for the impacted switch must still be updated.
345 if (failedSwitch != null && targetSw.equals(failedSwitch)
346 && dstSw != null) {
347 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
348 currentEcmpSpgMap.remove(targetSw);
349 log.debug("Updating ECMPspg for dst:{} removing failed "
350 + "target:{}", dstSw, targetSw);
351 return;
352 }
353 //linkfailed - update both sides
354 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
355 dstSw = route.get(1);
356 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
357 log.debug("Updating ECMPspg for dst:{} and target:{}", dstSw, targetSw);
358 } else {
359 success = fixHashGroupsForRoute(route, false);
360 if (success) {
361 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
362 if (dstSw != null) {
363 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
364 }
365 log.debug("Updating ECMPspg for target:{} and dst:{}",
366 targetSw, dstSw);
367 }
368 }
369 }
370 }
371
372 /**
373 * Edits hash groups in the src-switch (targetSw) of a route-path by
374 * calling the groupHandler to either add or remove buckets in an existing
375 * hash group.
376 *
377 * @param route a single list representing a route-path where the first element
378 * is the src-switch (targetSw) of the route-path and the
379 * second element is the dst-switch
380 * @param revoke true if buckets in the hash-groups need to be removed;
381 * false if buckets in the hash-groups need to be added
382 * @return true if the hash group editing is successful
383 */
384 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
385 boolean revoke) {
386 DeviceId targetSw = route.get(0);
387 if (route.size() < 2) {
388 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
389 return false;
390 }
391 DeviceId destSw = route.get(1);
392 log.debug("Processing fixHashGroupsForRoute: Target {} -> Dest {}",
393 targetSw, destSw);
394 boolean targetIsEdge = false;
395 try {
396 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
397 } catch (DeviceConfigNotFoundException e) {
398 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
399 + "continuing fixHash", targetSw);
400 }
401
402 // figure out the new next hops at the targetSw towards the destSw
403 Set<DeviceId> nextHops = new HashSet<>();
404 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(destSw);
405 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
406 ecmpSpg.getAllLearnedSwitchesAndVia();
407 for (Integer itrIdx : switchVia.keySet()) {
408 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
409 switchVia.get(itrIdx);
410 for (DeviceId target : swViaMap.keySet()) {
411 if (target.equals(targetSw)) {
412 // found the iteration where targetSw is reached- get nextHops
413 if (!targetIsEdge && itrIdx > 1) {
414 // optimization for spines to not use other leaves to get
415 // to a leaf to avoid loops
416 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
417 + " --> dstSw:{}", itrIdx, targetSw, destSw);
418 break;
419 }
420 for (ArrayList<DeviceId> via : swViaMap.get(target)) {
421 if (via.isEmpty()) {
422 nextHops.add(destSw);
423 } else {
424 // first elem is next-hop in each ECMP path
425 nextHops.add(via.get(0));
426 }
427 }
428 break;
429 }
430 }
431 }
432
433 // call group handler to change hash group at targetSw
434 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
435 if (grpHandler == null) {
436 log.warn("Cannot find grouphandler for dev:{} .. aborting"
437 + " {} hash group buckets for route:{} ", targetSw,
438 (revoke) ? "revoke" : "repopulate", route);
439 return false;
440 }
441 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
442 (revoke) ? "revoke" : "repopulating",
443 targetSw, destSw, nextHops);
444 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
445 destSw, true)
446 : grpHandler.fixHashGroups(targetSw, nextHops,
447 destSw, false);
448 }
449
450 /**
451 * Processes a set a route-path changes by reprogramming routing rules and
452 * creating new hash-groups if necessary.
453 *
454 * @param routeChanges a set of route-path changes, where each route-path is
455 * a list with its first element the src-switch of the path
456 * and the second element the dst-switch of the path.
457 * @return true if successful in repopulating routes
458 */
459 private boolean repopulateRoutingRulesForRoutes(Set<ArrayList<DeviceId>> routeChanges) {
sangho20eff1d2015-04-13 15:15:58 -0700460 rulePopulator.resetCounter();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700461 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
462 new HashMap<>();
Saurav Dasc88d4662017-05-15 15:34:25 -0700463 for (ArrayList<DeviceId> link: routeChanges) {
sangho834e4b02015-05-01 09:38:25 -0700464 // When only the source device is defined, reinstall routes to all other devices
sangho20eff1d2015-04-13 15:15:58 -0700465 if (link.size() == 1) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700466 log.debug("-- repopulateRoutingRulesForRoutes: running ECMP graph for device {}", link.get(0));
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530467 EcmpShortestPathGraph ecmpSpg = new EcmpShortestPathGraph(link.get(0), srManager);
Charles Chan93e71ba2016-04-29 14:38:22 -0700468 if (populateEcmpRoutingRules(link.get(0), ecmpSpg, ImmutableSet.of())) {
Saurav Das25190812016-05-27 13:54:07 -0700469 log.debug("Populating flow rules from all to dest:{} is successful",
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700470 link.get(0));
sangho20eff1d2015-04-13 15:15:58 -0700471 currentEcmpSpgMap.put(link.get(0), ecmpSpg);
Saurav Dasc88d4662017-05-15 15:34:25 -0700472 log.debug("Updating ECMPspg for dest:{}", link.get(0));
sangho52abe3a2015-05-05 14:13:34 -0700473 } else {
Saurav Das25190812016-05-27 13:54:07 -0700474 log.warn("Failed to populate the flow rules from all to dest:{}", link.get(0));
sangho52abe3a2015-05-05 14:13:34 -0700475 return false;
sangho20eff1d2015-04-13 15:15:58 -0700476 }
sangho45b009c2015-05-07 13:30:57 -0700477 } else {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700478 ArrayList<ArrayList<DeviceId>> deviceRoutes =
479 routesBydevice.get(link.get(1));
480 if (deviceRoutes == null) {
481 deviceRoutes = new ArrayList<>();
482 routesBydevice.put(link.get(1), deviceRoutes);
483 }
484 deviceRoutes.add(link);
485 }
486 }
487
488 for (DeviceId impactedDevice : routesBydevice.keySet()) {
489 ArrayList<ArrayList<DeviceId>> deviceRoutes =
490 routesBydevice.get(impactedDevice);
491 for (ArrayList<DeviceId> link: deviceRoutes) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700492 log.debug("-- repopulateRoutingRulesForRoutes {} -> {}",
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700493 link.get(0), link.get(1));
sangho45b009c2015-05-07 13:30:57 -0700494 DeviceId src = link.get(0);
495 DeviceId dst = link.get(1);
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530496 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dst);
sangho45b009c2015-05-07 13:30:57 -0700497 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
498 ecmpSpg.getAllLearnedSwitchesAndVia();
499 for (Integer itrIdx : switchVia.keySet()) {
500 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
501 switchVia.get(itrIdx);
502 for (DeviceId targetSw : swViaMap.keySet()) {
503 if (!targetSw.equals(src)) {
504 continue;
505 }
506 Set<DeviceId> nextHops = new HashSet<>();
507 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700508 // in this ECMP path to the targetSw, get the next hop
sangho45b009c2015-05-07 13:30:57 -0700509 if (via.isEmpty()) {
510 nextHops.add(dst);
511 } else {
512 nextHops.add(via.get(0));
513 }
514 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700515 if (!populateEcmpRoutingRulePartial(targetSw, dst,
516 nextHops, ImmutableSet.of())) {
sangho45b009c2015-05-07 13:30:57 -0700517 return false;
sangho20eff1d2015-04-13 15:15:58 -0700518 }
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700519 log.debug("Populating flow rules from {} to {} is successful",
520 targetSw, dst);
sangho20eff1d2015-04-13 15:15:58 -0700521 }
sangho20eff1d2015-04-13 15:15:58 -0700522 }
523 }
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700524 //Only if all the flows for all impacted routes to a
525 //specific target are pushed successfully, update the
Saurav Das4e3224f2016-11-29 14:27:25 -0800526 //ECMP graph for that target. Or else the next event
527 //would not see any changes in the ECMP graphs.
528 //In another case, the target switch has gone away, so
529 //routes can't be installed. In that case, the current map
530 //is updated here, without any flows being pushed.
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700531 currentEcmpSpgMap.put(impactedDevice,
532 updatedEcmpSpgMap.get(impactedDevice));
Saurav Dasc88d4662017-05-15 15:34:25 -0700533 log.debug("Updating ECMPspg for impacted dev:{}", impactedDevice);
sangho20eff1d2015-04-13 15:15:58 -0700534 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700535
536 processHashGroupChange(routeChanges, false, null);
537
sangho20eff1d2015-04-13 15:15:58 -0700538 return true;
539 }
540
Saurav Dasb5c236e2016-06-07 10:08:06 -0700541 /**
Saurav Das4e3224f2016-11-29 14:27:25 -0800542 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -0700543 * previous ecmp shortest-path graph exists for a switch in order to compute
544 * affected routes. If such a graph does not exist, the method returns null.
545 *
546 * @param linkFail the failed link
547 * @return the set of affected routes which may be empty if no routes were
548 * affected, or null if no previous ecmp spg was found for comparison
549 */
sangho20eff1d2015-04-13 15:15:58 -0700550 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
551
552 Set<ArrayList<DeviceId>> routes = new HashSet<>();
553
554 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700555 log.debug("Computing the impacted routes for device {} due to link fail",
556 sw.id());
Charles Chanc42e84e2015-10-20 16:24:19 -0700557 if (!srManager.mastershipService.isLocalMaster(sw.id())) {
Saurav Dasb5c236e2016-06-07 10:08:06 -0700558 log.debug("No mastership for {} .. skipping route optimization",
559 sw.id());
sangho20eff1d2015-04-13 15:15:58 -0700560 continue;
561 }
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530562 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id());
sangho20eff1d2015-04-13 15:15:58 -0700563 if (ecmpSpg == null) {
Saurav Dasb5c236e2016-06-07 10:08:06 -0700564 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
565 + " rerouting and opting for full-reroute", sw.id());
566 return null;
sangho20eff1d2015-04-13 15:15:58 -0700567 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700568 if (log.isDebugEnabled()) {
569 log.debug("Root switch: {}", sw.id());
570 log.debug(" Current/Existing SPG: {}", ecmpSpg);
571 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(sw.id()));
572 }
sangho20eff1d2015-04-13 15:15:58 -0700573 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
574 ecmpSpg.getAllLearnedSwitchesAndVia();
575 for (Integer itrIdx : switchVia.keySet()) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700576 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
sangho20eff1d2015-04-13 15:15:58 -0700577 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
578 switchVia.get(itrIdx);
579 for (DeviceId targetSw : swViaMap.keySet()) {
Saurav Das4e3224f2016-11-29 14:27:25 -0800580 DeviceId rootSw = sw.id();
Saurav Dasb5c236e2016-06-07 10:08:06 -0700581 if (log.isTraceEnabled()) {
Saurav Das4e3224f2016-11-29 14:27:25 -0800582 log.trace("TargetSwitch {} --> RootSwitch {}", targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -0700583 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
584 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -0800585 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -0700586 }
587 }
sangho20eff1d2015-04-13 15:15:58 -0700588 Set<ArrayList<DeviceId>> subLinks =
Saurav Das4e3224f2016-11-29 14:27:25 -0800589 computeLinks(targetSw, rootSw, swViaMap);
sangho20eff1d2015-04-13 15:15:58 -0700590 for (ArrayList<DeviceId> alink: subLinks) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700591 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
592 alink.get(1).equals(linkFail.dst().deviceId()))
593 ||
594 (alink.get(0).equals(linkFail.dst().deviceId()) &&
595 alink.get(1).equals(linkFail.src().deviceId()))) {
Saurav Das4e3224f2016-11-29 14:27:25 -0800596 log.debug("Impacted route:{}->{}", targetSw, rootSw);
sangho20eff1d2015-04-13 15:15:58 -0700597 ArrayList<DeviceId> aRoute = new ArrayList<>();
Saurav Dasc88d4662017-05-15 15:34:25 -0700598 aRoute.add(targetSw); // switch with rules to populate
599 aRoute.add(rootSw); // towards this destination
sangho20eff1d2015-04-13 15:15:58 -0700600 routes.add(aRoute);
601 break;
602 }
603 }
604 }
605 }
sangho45b009c2015-05-07 13:30:57 -0700606
sangho20eff1d2015-04-13 15:15:58 -0700607 }
608
609 return routes;
610 }
611
Saurav Das4e3224f2016-11-29 14:27:25 -0800612 /**
613 * Computes set of affected routes due to new links or failed switches.
614 *
615 * @return the set of affected routes which may be empty if no routes were
616 * affected
617 */
sangho20eff1d2015-04-13 15:15:58 -0700618 private Set<ArrayList<DeviceId>> computeRouteChange() {
619
Saurav Das4e3224f2016-11-29 14:27:25 -0800620 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
621 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -0700622
623 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das4e3224f2016-11-29 14:27:25 -0800624 DeviceId rootSw = sw.id();
625 log.debug("Computing the impacted routes for device {}", rootSw);
626 if (!srManager.mastershipService.isLocalMaster(rootSw)) {
Saurav Dasb5c236e2016-06-07 10:08:06 -0700627 log.debug("No mastership for {} ... skipping route optimization",
Saurav Das4e3224f2016-11-29 14:27:25 -0800628 rootSw);
sangho20eff1d2015-04-13 15:15:58 -0700629 continue;
630 }
Saurav Dasb5c236e2016-06-07 10:08:06 -0700631 if (log.isTraceEnabled()) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700632 log.trace("Device links for dev: {}", rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -0800633 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
Saurav Dasb5c236e2016-06-07 10:08:06 -0700634 log.trace("{} -> {} ", link.src().deviceId(), link.dst().deviceId());
635 }
sangho45b009c2015-05-07 13:30:57 -0700636 }
Saurav Das4e3224f2016-11-29 14:27:25 -0800637 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
638 if (currEcmpSpg == null) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700639 log.debug("No existing ECMP graph for device {}.. adding self as "
640 + "changed route", rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -0800641 changedRoutesBuilder.add(Lists.newArrayList(rootSw));
sangho20eff1d2015-04-13 15:15:58 -0700642 continue;
643 }
Saurav Das4e3224f2016-11-29 14:27:25 -0800644 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Dasc88d4662017-05-15 15:34:25 -0700645 if (log.isDebugEnabled()) {
646 log.debug("Root switch: {}", rootSw);
647 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
648 log.debug(" New/Updated SPG: {}", newEcmpSpg);
Saurav Das4e3224f2016-11-29 14:27:25 -0800649 }
650 // first use the updated/new map to compare to current/existing map
651 // as new links may have come up
652 changedRoutesBuilder.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
653 // then use the current/existing map to compare to updated/new map
654 // as switch may have been removed
655 changedRoutesBuilder.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
656 }
sangho20eff1d2015-04-13 15:15:58 -0700657
Saurav Das4e3224f2016-11-29 14:27:25 -0800658 Set<ArrayList<DeviceId>> changedRoutes = changedRoutesBuilder.build();
659 for (ArrayList<DeviceId> route: changedRoutes) {
660 log.debug("Route changes Target -> Root");
661 if (route.size() == 1) {
662 log.debug(" : all -> {}", route.get(0));
663 } else {
664 log.debug(" : {} -> {}", route.get(0), route.get(1));
665 }
666 }
667 return changedRoutes;
668 }
669
670 /**
671 * For the root switch, searches all the target nodes reachable in the base
672 * graph, and compares paths to the ones in the comp graph.
673 *
674 * @param base the graph that is indexed for all reachable target nodes
675 * from the root node
676 * @param comp the graph that the base graph is compared to
677 * @param rootSw both ecmp graphs are calculated for the root node
678 * @return all the routes that have changed in the base graph
679 */
680 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
681 EcmpShortestPathGraph comp,
682 DeviceId rootSw) {
683 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
684 ImmutableSet.builder();
685 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
686 base.getAllLearnedSwitchesAndVia();
687 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
688 comp.getAllLearnedSwitchesAndVia();
689 for (Integer itrIdx : baseMap.keySet()) {
690 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
691 baseMap.get(itrIdx);
692 for (DeviceId targetSw : baseViaMap.keySet()) {
693 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
694 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
695 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700696 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -0800697 ArrayList<DeviceId> route = new ArrayList<>();
698 route.add(targetSw);
699 route.add(rootSw);
700 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -0700701 }
702 }
sangho45b009c2015-05-07 13:30:57 -0700703 }
Saurav Das4e3224f2016-11-29 14:27:25 -0800704 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -0700705 }
706
707 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -0800708 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -0700709 for (Integer itrIdx : switchVia.keySet()) {
710 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
711 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -0800712 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -0700713 continue;
714 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -0800715 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -0700716 }
717 }
718
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -0700719 return null;
sangho20eff1d2015-04-13 15:15:58 -0700720 }
721
722 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
723 DeviceId dst,
724 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
725 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
726 for (ArrayList<DeviceId> via : viaMap.get(src)) {
727 DeviceId linkSrc = src;
728 DeviceId linkDst = dst;
729 for (DeviceId viaDevice: via) {
730 ArrayList<DeviceId> link = new ArrayList<>();
731 linkDst = viaDevice;
732 link.add(linkSrc);
733 link.add(linkDst);
734 subLinks.add(link);
735 linkSrc = viaDevice;
736 }
737 ArrayList<DeviceId> link = new ArrayList<>();
738 link.add(linkSrc);
739 link.add(dst);
740 subLinks.add(link);
741 }
742
743 return subLinks;
744 }
745
Charles Chan93e71ba2016-04-29 14:38:22 -0700746 /**
747 * Populate ECMP rules for subnets from all switches to destination.
748 *
749 * @param destSw Device ID of destination switch
750 * @param ecmpSPG ECMP shortest path graph
751 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
Saurav Dasc88d4662017-05-15 15:34:25 -0700752 * @return true if it succeeds in populating rules
Charles Chan93e71ba2016-04-29 14:38:22 -0700753 */
sangho20eff1d2015-04-13 15:15:58 -0700754 private boolean populateEcmpRoutingRules(DeviceId destSw,
Charles Chan93e71ba2016-04-29 14:38:22 -0700755 EcmpShortestPathGraph ecmpSPG,
Pier Ventre10bd8d12016-11-26 21:05:22 -0800756 Set<IpPrefix> subnets) {
sanghob35a6192015-04-01 13:05:26 -0700757
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700758 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = ecmpSPG
759 .getAllLearnedSwitchesAndVia();
sanghob35a6192015-04-01 13:05:26 -0700760 for (Integer itrIdx : switchVia.keySet()) {
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700761 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap = switchVia
762 .get(itrIdx);
sanghob35a6192015-04-01 13:05:26 -0700763 for (DeviceId targetSw : swViaMap.keySet()) {
sanghob35a6192015-04-01 13:05:26 -0700764 Set<DeviceId> nextHops = new HashSet<>();
Saurav Dasa07f2032015-10-19 14:37:36 -0700765 log.debug("** Iter: {} root: {} target: {}", itrIdx, destSw, targetSw);
sanghob35a6192015-04-01 13:05:26 -0700766 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
767 if (via.isEmpty()) {
768 nextHops.add(destSw);
769 } else {
770 nextHops.add(via.get(0));
771 }
772 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700773 if (!populateEcmpRoutingRulePartial(targetSw, destSw, nextHops, subnets)) {
sanghob35a6192015-04-01 13:05:26 -0700774 return false;
775 }
776 }
777 }
778
779 return true;
780 }
781
Charles Chan93e71ba2016-04-29 14:38:22 -0700782 /**
783 * Populate ECMP rules for subnets from target to destination via nexthops.
784 *
Saurav Das25190812016-05-27 13:54:07 -0700785 * @param targetSw Device ID of target switch in which rules will be programmed
786 * @param destSw Device ID of final destination switch to which the rules will forward
787 * @param nextHops List of next hops via which destSw will be reached
Charles Chan93e71ba2016-04-29 14:38:22 -0700788 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
Saurav Dasc88d4662017-05-15 15:34:25 -0700789 * @return true if it succees in populating rules
Charles Chan93e71ba2016-04-29 14:38:22 -0700790 */
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700791 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
792 DeviceId destSw,
Charles Chan93e71ba2016-04-29 14:38:22 -0700793 Set<DeviceId> nextHops,
Pier Ventre10bd8d12016-11-26 21:05:22 -0800794 Set<IpPrefix> subnets) {
sanghob35a6192015-04-01 13:05:26 -0700795 boolean result;
796
797 if (nextHops.isEmpty()) {
798 nextHops.add(destSw);
799 }
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700800 // If both target switch and dest switch are edge routers, then set IP
sangho52abe3a2015-05-05 14:13:34 -0700801 // rule for both subnet and router IP.
Charles Chan0b4e6182015-11-03 10:42:14 -0800802 boolean targetIsEdge;
803 boolean destIsEdge;
Pier Ventree0ae7a32016-11-23 09:57:42 -0800804 Ip4Address destRouterIpv4;
805 Ip6Address destRouterIpv6;
Charles Chan0b4e6182015-11-03 10:42:14 -0800806
807 try {
808 targetIsEdge = config.isEdgeDevice(targetSw);
809 destIsEdge = config.isEdgeDevice(destSw);
Pier Ventree0ae7a32016-11-23 09:57:42 -0800810 destRouterIpv4 = config.getRouterIpv4(destSw);
811 destRouterIpv6 = config.getRouterIpv6(destSw);
Charles Chan0b4e6182015-11-03 10:42:14 -0800812 } catch (DeviceConfigNotFoundException e) {
813 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
814 return false;
815 }
816
817 if (targetIsEdge && destIsEdge) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700818 subnets = (subnets != null && !subnets.isEmpty()) ? subnets
819 : config.getSubnets(destSw);
820 log.debug("* populateEcmpRoutingRulePartial in device {} towards {} "
821 + "for subnets {}", targetSw, destSw, subnets);
Charles Chan93e71ba2016-04-29 14:38:22 -0700822 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
823 destSw, nextHops);
sanghob35a6192015-04-01 13:05:26 -0700824 if (!result) {
825 return false;
826 }
Pier Ventree0ae7a32016-11-23 09:57:42 -0800827 IpPrefix routerIpPrefix = destRouterIpv4.toIpPrefix();
Saurav Dasc88d4662017-05-15 15:34:25 -0700828 log.debug("* populateEcmpRoutingRulePartial in device {} towards {} "
829 + "for router IP {}", targetSw, destSw, routerIpPrefix);
830 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
831 destSw, nextHops);
sanghob35a6192015-04-01 13:05:26 -0700832 if (!result) {
833 return false;
834 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700835 // If present we deal with IPv6 loopback.
Pier Ventree0ae7a32016-11-23 09:57:42 -0800836 if (destRouterIpv6 != null) {
837 routerIpPrefix = destRouterIpv6.toIpPrefix();
Saurav Dasc88d4662017-05-15 15:34:25 -0700838 log.debug("* populateEcmpRoutingRulePartial in device {} towards {}"
839 + " for v6 router IP {}", targetSw, destSw, routerIpPrefix);
840 result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix,
841 destSw, nextHops);
Pier Ventree0ae7a32016-11-23 09:57:42 -0800842 if (!result) {
843 return false;
844 }
845 }
sangho52abe3a2015-05-05 14:13:34 -0700846 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700847
848 if (!targetIsEdge && destIsEdge) {
849 // MPLS rules in all non-edge target devices
850 log.debug("* populateEcmpRoutingRulePartial in device{} towards {} for "
851 + "all MPLS rules", targetSw, destSw);
852 result = rulePopulator.populateMplsRule(targetSw, destSw, nextHops,
853 destRouterIpv4);
Pier Ventree0ae7a32016-11-23 09:57:42 -0800854 if (!result) {
855 return false;
856 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700857 if (destRouterIpv6 != null) {
858 result = rulePopulator.populateMplsRule(targetSw, destSw, nextHops,
859 destRouterIpv6);
860 if (!result) {
861 return false;
862 }
863 }
Pier Ventree0ae7a32016-11-23 09:57:42 -0800864 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700865
866 // To save on ECMP groups
867 // avoid MPLS rules in non-edge-devices to non-edge-devices
868 // avoid MPLS transit rules in edge-devices
869 // avoid loopback IP rules in edge-devices to non-edge-devices
sanghob35a6192015-04-01 13:05:26 -0700870 return true;
871 }
872
873 /**
Saurav Das018605f2017-02-18 14:05:44 -0800874 * Populates filtering rules for port, and punting rules
875 * for gateway IPs, loopback IPs and arp/ndp traffic.
876 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -0700877 *
878 * @param deviceId Switch ID to set the rules
879 */
Saurav Das822c4e22015-10-23 10:51:11 -0700880 public void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -0700881 // Although device is added, sometimes device store does not have the
882 // ports for this device yet. It results in missing filtering rules in the
883 // switch. We will attempt it a few times. If it still does not work,
884 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -0800885 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -0800886 if (firstRun == null) {
887 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -0700888 }
Saurav Dasd2fded02016-12-02 15:43:47 -0800889 executorService.schedule(new RetryFilters(deviceId, firstRun),
890 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -0700891 }
892
893 /**
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700894 * Start the flow rule population process if it was never started. The
895 * process finishes successfully when all flow rules are set and stops with
896 * ABORTED status when any groups required for flows is not set yet.
sanghob35a6192015-04-01 13:05:26 -0700897 */
898 public void startPopulationProcess() {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900899 statusLock.lock();
900 try {
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700901 if (populationStatus == Status.IDLE
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700902 || populationStatus == Status.SUCCEEDED
903 || populationStatus == Status.ABORTED) {
sanghob35a6192015-04-01 13:05:26 -0700904 populationStatus = Status.STARTED;
905 populateAllRoutingRules();
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700906 } else {
907 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
908 populationStatus);
sanghob35a6192015-04-01 13:05:26 -0700909 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900910 } finally {
911 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700912 }
913 }
914
915 /**
916 * Resume the flow rule population process if it was aborted for any reason.
917 * Mostly the process is aborted when the groups required are not set yet.
Saurav Dasa07f2032015-10-19 14:37:36 -0700918 * XXX is this called?
919 *
sanghob35a6192015-04-01 13:05:26 -0700920 */
921 public void resumePopulationProcess() {
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900922 statusLock.lock();
923 try {
sanghob35a6192015-04-01 13:05:26 -0700924 if (populationStatus == Status.ABORTED) {
925 populationStatus = Status.STARTED;
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700926 // TODO: we need to restart from the point aborted instead of
927 // restarting.
sanghob35a6192015-04-01 13:05:26 -0700928 populateAllRoutingRules();
929 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900930 } finally {
931 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700932 }
933 }
Saurav Das80980c72016-03-23 11:22:49 -0700934
Charles Chan93e71ba2016-04-29 14:38:22 -0700935 /**
936 * Populate rules of given subnet at given location.
937 *
938 * @param cp connect point of the subnet being added
939 * @param subnets subnet being added
940 * @return true if succeed
941 */
Pier Ventre10bd8d12016-11-26 21:05:22 -0800942 protected boolean populateSubnet(ConnectPoint cp, Set<IpPrefix> subnets) {
Charles Chan93e71ba2016-04-29 14:38:22 -0700943 statusLock.lock();
944 try {
945 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(cp.deviceId());
946 if (ecmpSpg == null) {
947 log.warn("Fail to populating subnet {}: {}", subnets, ECMPSPG_MISSING);
948 return false;
949 }
950 return populateEcmpRoutingRules(cp.deviceId(), ecmpSpg, subnets);
951 } finally {
952 statusLock.unlock();
953 }
954 }
955
956 /**
957 * Revoke rules of given subnet at given location.
958 *
959 * @param subnets subnet being removed
960 * @return true if succeed
961 */
Pier Ventre10bd8d12016-11-26 21:05:22 -0800962 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
Charles Chan93e71ba2016-04-29 14:38:22 -0700963 statusLock.lock();
964 try {
965 return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
966 } finally {
967 statusLock.unlock();
968 }
969 }
970
971 protected void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das80980c72016-03-23 11:22:49 -0700972 currentEcmpSpgMap.remove(deviceId);
Saurav Das7a1ffca2016-03-28 19:00:18 -0700973 if (updatedEcmpSpgMap != null) {
974 updatedEcmpSpgMap.remove(deviceId);
975 }
Saurav Das80980c72016-03-23 11:22:49 -0700976 }
Saurav Das59232cf2016-04-27 18:35:50 -0700977
Saurav Dasd2fded02016-12-02 15:43:47 -0800978 /**
979 * Utility class used to temporarily store information about the ports on a
980 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -0800981 */
982 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -0800983 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -0700984
Saurav Das018605f2017-02-18 14:05:44 -0800985 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -0800986 int filteredPorts) {
987 this.disabledPorts = disabledPorts;
988 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -0800989 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -0800990 }
991
992 @Override
993 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -0800994 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -0800995 }
996
997 @Override
998 public boolean equals(Object obj) {
999 if (this == obj) {
1000 return true;
1001 }
1002 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1003 return false;
1004 }
1005 PortFilterInfo other = (PortFilterInfo) obj;
1006 return ((disabledPorts == other.disabledPorts) &&
1007 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001008 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001009 }
1010
1011 @Override
1012 public String toString() {
1013 MoreObjects.ToStringHelper helper = toStringHelper(this)
1014 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001015 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001016 .add("filteredPorts", filteredPorts);
1017 return helper.toString();
1018 }
1019 }
1020
1021 /**
1022 * RetryFilters populates filtering objectives for a device and keeps retrying
1023 * till the number of ports filtered are constant for a predefined number
1024 * of attempts.
1025 */
1026 protected final class RetryFilters implements Runnable {
1027 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1028 DeviceId devId;
1029 int counter;
1030 PortFilterInfo prevRun;
1031
1032 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001033 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001034 prevRun = previousRun;
1035 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001036 }
1037
1038 @Override
1039 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001040 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001041 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001042 boolean sameResult = prevRun.equals(thisRun);
1043 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1044 thisRun, sameResult);
1045 if (thisRun == null || !sameResult || (sameResult && --constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001046 // exponentially increasing intervals for retries
1047 executorService.schedule(this,
1048 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1049 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001050 if (!sameResult) {
1051 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1052 }
Saurav Das59232cf2016-04-27 18:35:50 -07001053 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001054 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001055 }
Saurav Das59232cf2016-04-27 18:35:50 -07001056 }
1057
sanghob35a6192015-04-01 13:05:26 -07001058}