blob: f18143f3bc75accc45e014ec226dfff2d650476f [file] [log] [blame]
sangho80f11cb2015-04-01 13:05:26 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sangho80f11cb2015-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 Das62ae6792017-05-15 15:34:25 -070018import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableMap.Builder;
Charles Chanc22cef32016-04-29 14:38:22 -070020import com.google.common.collect.ImmutableSet;
Saurav Das1b391d52016-11-29 14:27:25 -080021import com.google.common.collect.Lists;
sanghofb7c7292015-04-13 15:15:58 -070022import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
Saurav Dasfbe74572017-08-03 18:30:35 -070024
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -070025import org.onlab.packet.EthType;
Charles Chan19b70032019-04-17 14:20:26 -070026import com.google.common.collect.Streams;
sangho9b169e32015-04-14 16:27:13 -070027import org.onlab.packet.Ip4Address;
Pier Ventreadb4ae62016-11-23 09:57:42 -080028import org.onlab.packet.Ip6Address;
sangho80f11cb2015-04-01 13:05:26 -070029import org.onlab.packet.IpPrefix;
Charles Chan910be6a2017-08-23 14:46:43 -070030import org.onlab.packet.MacAddress;
31import org.onlab.packet.VlanId;
Saurav Das261c3002017-06-13 15:35:54 -070032import org.onosproject.cluster.NodeId;
Saurav Das00e553b2018-04-21 17:19:48 -070033import org.onosproject.mastership.MastershipEvent;
Charles Chanc22cef32016-04-29 14:38:22 -070034import org.onosproject.net.ConnectPoint;
sangho80f11cb2015-04-01 13:05:26 -070035import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
sanghofb7c7292015-04-13 15:15:58 -070037import org.onosproject.net.Link;
Charles Chan910be6a2017-08-23 14:46:43 -070038import org.onosproject.net.PortNumber;
Charles Chan319d1a22015-11-03 10:42:14 -080039import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
40import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Das62ae6792017-05-15 15:34:25 -070041import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Charles Chand66d6712018-03-29 16:03:41 -070042import org.onosproject.store.serializers.KryoNamespaces;
43import org.onosproject.store.service.Serializer;
sangho80f11cb2015-04-01 13:05:26 -070044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -070047import java.time.Instant;
sangho80f11cb2015-04-01 13:05:26 -070048import java.util.ArrayList;
Charles Chand66d6712018-03-29 16:03:41 -070049import java.util.Collections;
sangho80f11cb2015-04-01 13:05:26 -070050import java.util.HashMap;
51import java.util.HashSet;
Saurav Das261c3002017-06-13 15:35:54 -070052import java.util.Iterator;
Charles Chand66d6712018-03-29 16:03:41 -070053import java.util.List;
Saurav Das261c3002017-06-13 15:35:54 -070054import java.util.Map;
Saurav Dasd1872b02016-12-02 15:43:47 -080055import java.util.Objects;
Charles Chan6dbcd252018-04-02 11:46:38 -070056import java.util.Optional;
sangho80f11cb2015-04-01 13:05:26 -070057import java.util.Set;
Saurav Das07c74602016-04-27 18:35:50 -070058import java.util.concurrent.ScheduledExecutorService;
59import java.util.concurrent.TimeUnit;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090060import java.util.concurrent.locks.Lock;
61import java.util.concurrent.locks.ReentrantLock;
Charles Chan19b70032019-04-17 14:20:26 -070062import java.util.stream.Collectors;
Saurav Dasdc7f2752018-03-18 21:28:15 -070063import java.util.stream.Stream;
64
Pier Ventreadb4ae62016-11-23 09:57:42 -080065import static com.google.common.base.Preconditions.checkNotNull;
66import static java.util.concurrent.Executors.newScheduledThreadPool;
67import static org.onlab.util.Tools.groupedThreads;
sangho80f11cb2015-04-01 13:05:26 -070068
Charles Chanb7f75ac2016-01-11 18:28:54 -080069/**
70 * Default routing handler that is responsible for route computing and
71 * routing rule population.
72 */
sangho80f11cb2015-04-01 13:05:26 -070073public class DefaultRoutingHandler {
Saurav Dasf9332192017-02-18 14:05:44 -080074 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey092e9e22018-02-01 13:49:47 -080075 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Dasf9332192017-02-18 14:05:44 -080076 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasfbe74572017-08-03 18:30:35 -070077 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das00e553b2018-04-21 17:19:48 -070078 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Saurav Das68e1b6a2018-06-11 17:02:31 -070079 private static final long PURGE_DELAY = 1000; // ms
Charles Chanc22cef32016-04-29 14:38:22 -070080 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sangho80f11cb2015-04-01 13:05:26 -070081
82 private SegmentRoutingManager srManager;
83 private RoutingRulePopulator rulePopulator;
Shashikanth VH0637b162015-12-11 01:32:44 +053084 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
85 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho9b169e32015-04-14 16:27:13 -070086 private DeviceConfiguration config;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090087 private final Lock statusLock = new ReentrantLock();
88 private volatile Status populationStatus;
Yuta HIGUCHIebee2f12016-07-21 16:54:33 -070089 private ScheduledExecutorService executorService
Saurav Dasd1872b02016-12-02 15:43:47 -080090 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070091 private ScheduledExecutorService executorServiceMstChg
92 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
Saurav Das68e1b6a2018-06-11 17:02:31 -070093 private ScheduledExecutorService executorServiceFRR
94 = newScheduledThreadPool(1, groupedThreads("fullRR", "fullRR-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070095
Saurav Das00e553b2018-04-21 17:19:48 -070096 private Instant lastRoutingChange = Instant.EPOCH;
Saurav Das68e1b6a2018-06-11 17:02:31 -070097 private Instant lastFullReroute = Instant.EPOCH;
sangho80f11cb2015-04-01 13:05:26 -070098
Saurav Das00e553b2018-04-21 17:19:48 -070099 // Distributed store to keep track of ONOS instance that should program the
100 // device pair. There should be only one instance (the king) that programs the same pair.
Charles Chand66d6712018-03-29 16:03:41 -0700101 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chanfbcb8812018-04-18 18:41:05 -0700102 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chand66d6712018-03-29 16:03:41 -0700103
Saurav Das00e553b2018-04-21 17:19:48 -0700104 // Local store to keep track of all devices that this instance was responsible
105 // for programming in the last run. Helps to determine if mastership changed
106 // during a run - only relevant for programming as a result of topo change.
107 Set<DeviceId> lastProgrammed;
108
sangho80f11cb2015-04-01 13:05:26 -0700109 /**
110 * Represents the default routing population status.
111 */
112 public enum Status {
113 // population process is not started yet.
114 IDLE,
115
116 // population process started.
117 STARTED,
118
Srikanth Vavilapalli64505482015-04-21 13:04:13 -0700119 // population process was aborted due to errors, mostly for groups not
120 // found.
sangho80f11cb2015-04-01 13:05:26 -0700121 ABORTED,
122
123 // population process was finished successfully.
124 SUCCEEDED
125 }
126
127 /**
128 * Creates a DefaultRoutingHandler object.
129 *
130 * @param srManager SegmentRoutingManager object
131 */
Charles Chand66d6712018-03-29 16:03:41 -0700132 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700133 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
134 .withName("sr-should-program")
135 .withSerializer(Serializer.using(KryoNamespaces.API))
136 .withRelaxedReadConsistency()
137 .build().asJavaMap();
138 this.shouldProgramCache = Maps.newConcurrentMap();
139 update(srManager);
140 }
141
142 /**
143 * Updates a DefaultRoutingHandler object.
144 *
145 * @param srManager SegmentRoutingManager object
146 */
147 void update(SegmentRoutingManager srManager) {
sangho80f11cb2015-04-01 13:05:26 -0700148 this.srManager = srManager;
149 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho9b169e32015-04-14 16:27:13 -0700150 this.config = checkNotNull(srManager.deviceConfiguration);
sangho80f11cb2015-04-01 13:05:26 -0700151 this.populationStatus = Status.IDLE;
sanghofb7c7292015-04-13 15:15:58 -0700152 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das00e553b2018-04-21 17:19:48 -0700153 this.lastProgrammed = Sets.newConcurrentHashSet();
sangho80f11cb2015-04-01 13:05:26 -0700154 }
155
156 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700157 * Returns an immutable copy of the current ECMP shortest-path graph as
158 * computed by this controller instance.
159 *
Saurav Das261c3002017-06-13 15:35:54 -0700160 * @return immutable copy of the current ECMP graph
Saurav Das62ae6792017-05-15 15:34:25 -0700161 */
162 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
163 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
164 currentEcmpSpgMap.entrySet().forEach(entry -> {
165 if (entry.getValue() != null) {
166 builder.put(entry.getKey(), entry.getValue());
167 }
168 });
169 return builder.build();
170 }
171
Saurav Dasfbe74572017-08-03 18:30:35 -0700172 /**
173 * Acquires the lock used when making routing changes.
174 */
175 public void acquireRoutingLock() {
176 statusLock.lock();
177 }
178
179 /**
180 * Releases the lock used when making routing changes.
181 */
182 public void releaseRoutingLock() {
183 statusLock.unlock();
184 }
185
186 /**
187 * Determines if routing in the network has been stable in the last
188 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
189 * routing change timestamp.
190 *
191 * @return true if stable
192 */
193 public boolean isRoutingStable() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700194 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
195 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700196 log.trace("Routing stable since {}s", now - last);
Saurav Dasfbe74572017-08-03 18:30:35 -0700197 return (now - last) > STABLITY_THRESHOLD;
198 }
199
Saurav Das49368392018-04-23 18:42:12 -0700200 /**
201 * Gracefully shuts down the defaultRoutingHandler. Typically called when
202 * the app is deactivated
203 */
204 public void shutdown() {
205 executorService.shutdown();
206 executorServiceMstChg.shutdown();
Saurav Das68e1b6a2018-06-11 17:02:31 -0700207 executorServiceFRR.shutdown();
Saurav Das49368392018-04-23 18:42:12 -0700208 }
Saurav Dasfbe74572017-08-03 18:30:35 -0700209
Saurav Das261c3002017-06-13 15:35:54 -0700210 //////////////////////////////////////
211 // Route path handling
212 //////////////////////////////////////
213
Saurav Dase6c448a2018-01-18 12:07:33 -0800214 /* The following three methods represent the three major ways in which
215 * route-path handling is triggered in the network
Saurav Das261c3002017-06-13 15:35:54 -0700216 * a) due to configuration change
217 * b) due to route-added event
218 * c) due to change in the topology
219 */
220
Saurav Das62ae6792017-05-15 15:34:25 -0700221 /**
Saurav Das261c3002017-06-13 15:35:54 -0700222 * Populates all routing rules to all switches. Typically triggered at
223 * startup or after a configuration event.
sangho80f11cb2015-04-01 13:05:26 -0700224 */
Saurav Das62ae6792017-05-15 15:34:25 -0700225 public void populateAllRoutingRules() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700226 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900227 statusLock.lock();
228 try {
Saurav Das261c3002017-06-13 15:35:54 -0700229 if (populationStatus == Status.STARTED) {
230 log.warn("Previous rule population is not finished. Cannot"
231 + " proceed with populateAllRoutingRules");
232 return;
233 }
234
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900235 populationStatus = Status.STARTED;
236 rulePopulator.resetCounter();
Saurav Das261c3002017-06-13 15:35:54 -0700237 log.info("Starting to populate all routing rules");
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900238 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sangho80f11cb2015-04-01 13:05:26 -0700239
Saurav Das261c3002017-06-13 15:35:54 -0700240 // take a snapshot of the topology
241 updatedEcmpSpgMap = new HashMap<>();
242 Set<EdgePair> edgePairs = new HashSet<>();
243 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart61e24e12017-11-30 18:23:42 -0800244 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das261c3002017-06-13 15:35:54 -0700245 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart61e24e12017-11-30 18:23:42 -0800246 new EcmpShortestPathGraph(dstSw, srManager);
247 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700248 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
249 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700250 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700251 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
252 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
253 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700254 }
Charles Chand66d6712018-03-29 16:03:41 -0700255
256 if (!shouldProgram(dstSw)) {
Saurav Das00e553b2018-04-21 17:19:48 -0700257 lastProgrammed.remove(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900258 continue;
Saurav Das00e553b2018-04-21 17:19:48 -0700259 } else {
260 lastProgrammed.add(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900261 }
Saurav Das00e553b2018-04-21 17:19:48 -0700262 // To do a full reroute, assume all route-paths have changed
Charles Chand66d6712018-03-29 16:03:41 -0700263 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart61e24e12017-11-30 18:23:42 -0800264 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
265 if (targetSw.equals(dev)) {
Saurav Das261c3002017-06-13 15:35:54 -0700266 continue;
267 }
Jonathan Hart61e24e12017-11-30 18:23:42 -0800268 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das261c3002017-06-13 15:35:54 -0700269 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900270 }
Saurav Das261c3002017-06-13 15:35:54 -0700271 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900272
Saurav Das261c3002017-06-13 15:35:54 -0700273 if (!redoRouting(routeChanges, edgePairs, null)) {
274 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
275 populationStatus = Status.ABORTED;
276 log.warn("Failed to repopulate all routing rules.");
277 return;
sangho80f11cb2015-04-01 13:05:26 -0700278 }
279
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900280 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
281 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700282 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900283 rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700284 return;
pierdebd15c2019-04-19 20:55:53 +0200285 } catch (Exception e) {
286 log.error("populateAllRoutingRules thrown an exception: {}",
287 e.getMessage(), e);
288 populationStatus = Status.ABORTED;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900289 } finally {
290 statusLock.unlock();
sangho80f11cb2015-04-01 13:05:26 -0700291 }
sangho80f11cb2015-04-01 13:05:26 -0700292 }
293
sanghofb7c7292015-04-13 15:15:58 -0700294 /**
Saurav Das261c3002017-06-13 15:35:54 -0700295 * Populate rules from all other edge devices to the connect-point(s)
296 * specified for the given subnets.
297 *
298 * @param cpts connect point(s) of the subnets being added
299 * @param subnets subnets being added
Charles Chan910be6a2017-08-23 14:46:43 -0700300 */
301 // XXX refactor
Saurav Das261c3002017-06-13 15:35:54 -0700302 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan6db55b92017-09-11 15:21:57 -0700303 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
304 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
305 return;
306 }
307
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700308 lastRoutingChange = Instant.now();
Saurav Das261c3002017-06-13 15:35:54 -0700309 statusLock.lock();
310 try {
311 if (populationStatus == Status.STARTED) {
312 log.warn("Previous rule population is not finished. Cannot"
313 + " proceed with routing rules for added routes");
314 return;
315 }
316 populationStatus = Status.STARTED;
317 rulePopulator.resetCounter();
Charles Chan910be6a2017-08-23 14:46:43 -0700318 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
319 subnets, cpts);
Saurav Das6430f412018-01-25 09:49:01 -0800320 // In principle an update to a subnet/prefix should not require a
321 // new ECMPspg calculation as it is not a topology event. As a
322 // result, we use the current/existing ECMPspg in the updated map
323 // used by the redoRouting method.
Saurav Das6de6ffd2018-02-09 09:15:03 -0800324 if (updatedEcmpSpgMap == null) {
325 updatedEcmpSpgMap = new HashMap<>();
326 }
Saurav Das6430f412018-01-25 09:49:01 -0800327 currentEcmpSpgMap.entrySet().forEach(entry -> {
328 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase321cff2018-02-09 17:26:45 -0800329 if (log.isTraceEnabled()) {
330 log.trace("Root switch: {}", entry.getKey());
331 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Das6430f412018-01-25 09:49:01 -0800332 }
333 });
Saurav Das261c3002017-06-13 15:35:54 -0700334 Set<EdgePair> edgePairs = new HashSet<>();
335 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
336 boolean handleRouting = false;
337
338 if (cpts.size() == 2) {
339 // ensure connect points are edge-pairs
340 Iterator<ConnectPoint> iter = cpts.iterator();
341 DeviceId dev1 = iter.next().deviceId();
Charles Chan6dbcd252018-04-02 11:46:38 -0700342 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
343 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
344 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700345 } else {
346 log.warn("Connectpoints {} for subnets {} not on "
347 + "pair-devices.. aborting populateSubnet", cpts, subnets);
348 populationStatus = Status.ABORTED;
349 return;
350 }
351 for (ConnectPoint cp : cpts) {
Saurav Das6430f412018-01-25 09:49:01 -0800352 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
353 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700354 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800355 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
356 log.warn("populateSubnet: no updated graph for dev:{}"
357 + " ... creating", cp.deviceId());
358 }
Charles Chand66d6712018-03-29 16:03:41 -0700359 if (!shouldProgram(cp.deviceId())) {
Saurav Das261c3002017-06-13 15:35:54 -0700360 continue;
361 }
362 handleRouting = true;
363 }
364 } else {
365 // single connect point
366 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Das6430f412018-01-25 09:49:01 -0800367 if (updatedEcmpSpgMap.get(dstSw) == null) {
368 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700369 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800370 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
371 log.warn("populateSubnet: no updated graph for dev:{}"
372 + " ... creating", dstSw);
373 }
Charles Chand66d6712018-03-29 16:03:41 -0700374 handleRouting = shouldProgram(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700375 }
376
377 if (!handleRouting) {
378 log.debug("This instance is not handling ecmp routing to the "
379 + "connectPoint(s) {}", cpts);
380 populationStatus = Status.ABORTED;
381 return;
382 }
383
384 // if it gets here, this instance should handle routing for the
385 // connectpoint(s). Assume all route-paths have to be updated to
386 // the connectpoint(s) with the following exceptions
387 // 1. if target is non-edge no need for routing rules
388 // 2. if target is one of the connectpoints
389 for (ConnectPoint cp : cpts) {
390 DeviceId dstSw = cp.deviceId();
391 for (Device targetSw : srManager.deviceService.getDevices()) {
392 boolean isEdge = false;
393 try {
394 isEdge = config.isEdgeDevice(targetSw.id());
395 } catch (DeviceConfigNotFoundException e) {
Charles Chaneaf3c9b2018-02-16 17:20:54 -0800396 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
397 continue;
Saurav Das261c3002017-06-13 15:35:54 -0700398 }
Charles Chan6dbcd252018-04-02 11:46:38 -0700399 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700400 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chan6dbcd252018-04-02 11:46:38 -0700401 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das261c3002017-06-13 15:35:54 -0700402 continue;
403 }
404 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
405 }
406 }
407
408 if (!redoRouting(routeChanges, edgePairs, subnets)) {
409 log.debug("populateSubnet: populationStatus is ABORTED");
410 populationStatus = Status.ABORTED;
411 log.warn("Failed to repopulate the rules for subnet.");
412 return;
413 }
414
415 log.debug("populateSubnet: populationStatus is SUCCEEDED");
416 populationStatus = Status.SUCCEEDED;
417 log.info("Completed subnet population. Total # of rules pushed : {}",
418 rulePopulator.getCounter());
419 return;
420
pierdebd15c2019-04-19 20:55:53 +0200421 } catch (Exception e) {
422 log.error("populateSubnet thrown an exception: {}",
423 e.getMessage(), e);
424 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700425 } finally {
426 statusLock.unlock();
427 }
428 }
429
430 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700431 * Populates the routing rules or makes hash group changes according to the
432 * route-path changes due to link failure, switch failure or link up. This
433 * method should only be called for one of these three possible event-types.
Saurav Dasdc7f2752018-03-18 21:28:15 -0700434 * Note that when a switch goes away, all of its links fail as well, but
435 * this is handled as a single switch removal event.
sanghofb7c7292015-04-13 15:15:58 -0700436 *
Saurav Dasdc7f2752018-03-18 21:28:15 -0700437 * @param linkDown the single failed link, or null for other conditions such
438 * as link-up or a removed switch
Saurav Das62ae6792017-05-15 15:34:25 -0700439 * @param linkUp the single link up, or null for other conditions such as
Saurav Dasdc7f2752018-03-18 21:28:15 -0700440 * link-down or a removed switch
441 * @param switchDown the removed switch, or null for other conditions such
442 * as link-down or link-up
443 * @param seenBefore true if this event is for a linkUp or linkDown for a
444 * seen link
445 */
446 // TODO This method should be refactored into three separated methods
Charles Chan9d2dd552018-06-19 20:56:33 -0700447 public void populateRoutingRulesForLinkStatusChange(Link linkDown, Link linkUp,
448 DeviceId switchDown, boolean seenBefore) {
Saurav Dasdc7f2752018-03-18 21:28:15 -0700449 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
450 .count() != 1) {
Saurav Das62ae6792017-05-15 15:34:25 -0700451 log.warn("Only one event can be handled for link status change .. aborting");
452 return;
453 }
Saurav Dasdc7f2752018-03-18 21:28:15 -0700454
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700455 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900456 statusLock.lock();
457 try {
sanghofb7c7292015-04-13 15:15:58 -0700458
459 if (populationStatus == Status.STARTED) {
Saurav Das261c3002017-06-13 15:35:54 -0700460 log.warn("Previous rule population is not finished. Cannot"
Saurav Das6430f412018-01-25 09:49:01 -0800461 + " proceeed with routingRules for Topology change");
Saurav Das62ae6792017-05-15 15:34:25 -0700462 return;
sanghofb7c7292015-04-13 15:15:58 -0700463 }
464
Saurav Das261c3002017-06-13 15:35:54 -0700465 // Take snapshots of the topology
sangho28d0b6d2015-05-07 13:30:57 -0700466 updatedEcmpSpgMap = new HashMap<>();
Saurav Das261c3002017-06-13 15:35:54 -0700467 Set<EdgePair> edgePairs = new HashSet<>();
sangho28d0b6d2015-05-07 13:30:57 -0700468 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH0637b162015-12-11 01:32:44 +0530469 EcmpShortestPathGraph ecmpSpgUpdated =
470 new EcmpShortestPathGraph(sw.id(), srManager);
sangho28d0b6d2015-05-07 13:30:57 -0700471 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700472 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
473 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700474 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700475 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
476 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
477 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700478 }
sangho28d0b6d2015-05-07 13:30:57 -0700479 }
480
Saurav Das6430f412018-01-25 09:49:01 -0800481 log.info("Starting to populate routing rules from Topology change");
sanghodf0153f2015-05-05 14:13:34 -0700482
sanghofb7c7292015-04-13 15:15:58 -0700483 Set<ArrayList<DeviceId>> routeChanges;
Saurav Das62ae6792017-05-15 15:34:25 -0700484 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700485 + "populationStatus is STARTED");
sanghofb7c7292015-04-13 15:15:58 -0700486 populationStatus = Status.STARTED;
Saurav Das6430f412018-01-25 09:49:01 -0800487 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
488 boolean hashGroupsChanged = false;
Saurav Das1b391d52016-11-29 14:27:25 -0800489 // try optimized re-routing
Saurav Das62ae6792017-05-15 15:34:25 -0700490 if (linkDown == null) {
491 // either a linkUp or a switchDown - compute all route changes by
492 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dascea556f2018-03-05 14:37:16 -0800493 routeChanges = computeRouteChange(switchDown);
Saurav Das62ae6792017-05-15 15:34:25 -0700494
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700495 // deal with linkUp of a seen-before link
Saurav Dasdc7f2752018-03-18 21:28:15 -0700496 if (linkUp != null && seenBefore) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700497 // link previously seen before
498 // do hash-bucket changes instead of a re-route
499 processHashGroupChange(routeChanges, false, null);
500 // clear out routesChanges so a re-route is not attempted
501 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800502 hashGroupsChanged = true;
Saurav Das62ae6792017-05-15 15:34:25 -0700503 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700504 // for a linkUp of a never-seen-before link
505 // let it fall through to a reroute of the routeChanges
Saurav Das62ae6792017-05-15 15:34:25 -0700506
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700507 //deal with switchDown
508 if (switchDown != null) {
509 processHashGroupChange(routeChanges, true, switchDown);
510 // clear out routesChanges so a re-route is not attempted
511 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800512 hashGroupsChanged = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700513 }
sanghofb7c7292015-04-13 15:15:58 -0700514 } else {
Saurav Das62ae6792017-05-15 15:34:25 -0700515 // link has gone down
516 // Compare existing ECMP SPG only with the link that went down
517 routeChanges = computeDamagedRoutes(linkDown);
Saurav Das68e1b6a2018-06-11 17:02:31 -0700518 processHashGroupChange(routeChanges, true, null);
519 // clear out routesChanges so a re-route is not attempted
520 routeChanges = ImmutableSet.of();
521 hashGroupsChanged = true;
Saurav Dasb149be12016-06-07 10:08:06 -0700522 }
523
sanghofb7c7292015-04-13 15:15:58 -0700524 if (routeChanges.isEmpty()) {
Saurav Das6430f412018-01-25 09:49:01 -0800525 if (hashGroupsChanged) {
526 log.info("Hash-groups changed for link status change");
527 } else {
528 log.info("No re-route or re-hash attempted for the link"
529 + " status change");
530 updatedEcmpSpgMap.keySet().forEach(devId -> {
531 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
532 log.debug("Updating ECMPspg for remaining dev:{}", devId);
533 });
534 }
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700535 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700536 populationStatus = Status.SUCCEEDED;
Saurav Das62ae6792017-05-15 15:34:25 -0700537 return;
sanghofb7c7292015-04-13 15:15:58 -0700538 }
539
Saurav Das62ae6792017-05-15 15:34:25 -0700540 // reroute of routeChanges
Saurav Das261c3002017-06-13 15:35:54 -0700541 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700542 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700543 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700544 log.info("Completed repopulation of rules for link-status change."
545 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700546 return;
sanghofb7c7292015-04-13 15:15:58 -0700547 } else {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700548 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sanghofb7c7292015-04-13 15:15:58 -0700549 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700550 log.warn("Failed to repopulate the rules for link status change.");
Saurav Das62ae6792017-05-15 15:34:25 -0700551 return;
sanghofb7c7292015-04-13 15:15:58 -0700552 }
pierdebd15c2019-04-19 20:55:53 +0200553 } catch (Exception e) {
554 log.error("populateRoutingRulesForLinkStatusChange thrown an exception: {}",
555 e.getMessage(), e);
556 populationStatus = Status.ABORTED;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900557 } finally {
558 statusLock.unlock();
sanghofb7c7292015-04-13 15:15:58 -0700559 }
560 }
561
Saurav Das62ae6792017-05-15 15:34:25 -0700562 /**
Saurav Das261c3002017-06-13 15:35:54 -0700563 * Processes a set a route-path changes by reprogramming routing rules and
564 * creating new hash-groups or editing them if necessary. This method also
565 * determines the next-hops for the route-path from the src-switch (target)
566 * of the path towards the dst-switch of the path.
Saurav Das62ae6792017-05-15 15:34:25 -0700567 *
Saurav Das261c3002017-06-13 15:35:54 -0700568 * @param routeChanges a set of route-path changes, where each route-path is
569 * a list with its first element the src-switch (target)
570 * of the path, and the second element the dst-switch of
571 * the path.
572 * @param edgePairs a set of edge-switches that are paired by configuration
573 * @param subnets a set of prefixes that need to be populated in the routing
574 * table of the target switch in the route-path. Can be null,
575 * in which case all the prefixes belonging to the dst-switch
576 * will be populated in the target switch
577 * @return true if successful in repopulating all routes
Saurav Das62ae6792017-05-15 15:34:25 -0700578 */
Saurav Das261c3002017-06-13 15:35:54 -0700579 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
580 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
581 // first make every entry two-elements
582 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
583 for (ArrayList<DeviceId> route : routeChanges) {
584 if (route.size() == 1) {
585 DeviceId dstSw = route.get(0);
586 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
587 if (ec == null) {
588 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
589 return false;
590 }
591 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
592 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
593 changedRoutes.add(Lists.newArrayList(target, dstSw));
594 });
595 });
596 } else {
597 DeviceId targetSw = route.get(0);
598 DeviceId dstSw = route.get(1);
599 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
600 }
601 }
602
603 // now process changedRoutes according to edgePairs
604 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
605 return false; //abort routing and fail fast
606 }
607
608 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Das6430f412018-01-25 09:49:01 -0800609 Set<DeviceId> updatedDevices = Sets.newHashSet();
610 if (!redoRoutingIndividualDests(subnets, changedRoutes,
611 updatedDevices)) {
Saurav Das261c3002017-06-13 15:35:54 -0700612 return false; //abort routing and fail fast
613 }
614
Saurav Das261c3002017-06-13 15:35:54 -0700615 // update ecmpSPG for all edge-pairs
616 for (EdgePair ep : edgePairs) {
617 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
618 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
619 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
620 }
Saurav Das6430f412018-01-25 09:49:01 -0800621
622 // here is where we update all devices not touched by this instance
623 updatedEcmpSpgMap.keySet().stream()
624 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
625 .filter(devId -> !updatedDevices.contains(devId))
626 .forEach(devId -> {
627 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
628 log.debug("Updating ECMPspg for remaining dev:{}", devId);
629 });
Saurav Das261c3002017-06-13 15:35:54 -0700630 return true;
631 }
632
633 /**
634 * Programs targetSw in the changedRoutes for given prefixes reachable by
635 * an edgePair. If no prefixes are given, the method will use configured
636 * subnets/prefixes. If some configured subnets belong only to a specific
637 * destination in the edgePair, then the target switch will be programmed
638 * only to that destination.
639 *
640 * @param edgePairs set of edge-pairs for which target will be programmed
641 * @param subnets a set of prefixes that need to be populated in the routing
642 * table of the target switch in the changedRoutes. Can be null,
643 * in which case all the configured prefixes belonging to the
644 * paired switches will be populated in the target switch
645 * @param changedRoutes a set of route-path changes, where each route-path is
646 * a list with its first element the src-switch (target)
647 * of the path, and the second element the dst-switch of
648 * the path.
649 * @return true if successful
650 */
651 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
652 Set<IpPrefix> subnets,
653 Set<ArrayList<DeviceId>> changedRoutes) {
654 for (EdgePair ep : edgePairs) {
655 // temp store for a target's changedRoutes to this edge-pair
656 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
657 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
658 while (i.hasNext()) {
659 ArrayList<DeviceId> route = i.next();
660 DeviceId dstSw = route.get(1);
661 if (ep.includes(dstSw)) {
662 // routeChange for edge pair found
663 // sort by target iff target is edge and remove from changedRoutes
664 DeviceId targetSw = route.get(0);
665 try {
666 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
667 continue;
668 }
669 } catch (DeviceConfigNotFoundException e) {
670 log.warn(e.getMessage() + "aborting redoRouting");
671 return false;
672 }
673 // route is from another edge to this edge-pair
674 if (targetRoutes.containsKey(targetSw)) {
675 targetRoutes.get(targetSw).add(route);
676 } else {
677 Set<ArrayList<DeviceId>> temp = new HashSet<>();
678 temp.add(route);
679 targetRoutes.put(targetSw, temp);
680 }
681 i.remove();
682 }
683 }
684 // so now for this edgepair we have a per target set of routechanges
685 // process target->edgePair route
686 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
687 targetRoutes.entrySet()) {
688 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
689 entry.getKey(), ep);
690 DeviceId targetSw = entry.getKey();
691 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
692 entry.getValue().forEach(route -> {
693 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
694 log.debug("route: target {} -> dst {} found with next-hops {}",
695 route.get(0), route.get(1), nhops);
696 perDstNextHops.put(route.get(1), nhops);
697 });
Charles Chan19b70032019-04-17 14:20:26 -0700698
699 List<Set<IpPrefix>> batchedSubnetDev1, batchedSubnetDev2;
700 if (subnets != null) {
701 batchedSubnetDev1 = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
702 batchedSubnetDev2 = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
703 } else {
704 batchedSubnetDev1 = config.getBatchedSubnets(ep.dev1);
705 batchedSubnetDev2 = config.getBatchedSubnets(ep.dev2);
706 }
707 List<Set<IpPrefix>> batchedSubnetBoth = Streams
708 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.intersection(a, b))
709 .filter(set -> !set.isEmpty())
710 .collect(Collectors.toList());
711 List<Set<IpPrefix>> batchedSubnetDev1Only = Streams
712 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.difference(a, b))
713 .filter(set -> !set.isEmpty())
714 .collect(Collectors.toList());
715 List<Set<IpPrefix>> batchedSubnetDev2Only = Streams
716 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.difference(b, a))
717 .filter(set -> !set.isEmpty())
718 .collect(Collectors.toList());
719
Saurav Das6430f412018-01-25 09:49:01 -0800720 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
721 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Charles Chan19b70032019-04-17 14:20:26 -0700722
Saurav Das261c3002017-06-13 15:35:54 -0700723 // handle routing to subnets common to edge-pair
Saurav Das6430f412018-01-25 09:49:01 -0800724 // only if the targetSw is not part of the edge-pair and there
725 // exists a next hop to at least one of the devices in the edge-pair
726 if (!ep.includes(targetSw)
Charles Chan19b70032019-04-17 14:20:26 -0700727 && ((nhDev1 != null && !nhDev1.isEmpty()) || (nhDev2 != null && !nhDev2.isEmpty()))) {
728 log.trace("getSubnets on both {} and {}: {}", ep.dev1, ep.dev2, batchedSubnetBoth);
729 for (Set<IpPrefix> prefixes : batchedSubnetBoth) {
730 if (!populateEcmpRoutingRulePartial(
731 targetSw,
732 ep.dev1, ep.dev2,
733 perDstNextHops,
734 prefixes)) {
735 return false; // abort everything and fail fast
736 }
Saurav Das261c3002017-06-13 15:35:54 -0700737 }
Charles Chan19b70032019-04-17 14:20:26 -0700738
Saurav Das261c3002017-06-13 15:35:54 -0700739 }
Saurav Das6430f412018-01-25 09:49:01 -0800740 // handle routing to subnets that only belong to dev1 only if
741 // a next-hop exists from the target to dev1
Charles Chan19b70032019-04-17 14:20:26 -0700742 if (!batchedSubnetDev1Only.isEmpty() &&
743 batchedSubnetDev1Only.stream().anyMatch(subnet -> !subnet.isEmpty()) &&
744 nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700745 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800746 onlyDev1NextHops.put(ep.dev1, nhDev1);
Charles Chan19b70032019-04-17 14:20:26 -0700747 log.trace("getSubnets on {} only: {}", ep.dev1, batchedSubnetDev1Only);
748 for (Set<IpPrefix> prefixes : batchedSubnetDev1Only) {
749 if (!populateEcmpRoutingRulePartial(
750 targetSw,
751 ep.dev1, null,
752 onlyDev1NextHops,
753 prefixes)) {
754 return false; // abort everything and fail fast
755 }
Saurav Das261c3002017-06-13 15:35:54 -0700756 }
757 }
Saurav Das6430f412018-01-25 09:49:01 -0800758 // handle routing to subnets that only belong to dev2 only if
759 // a next-hop exists from the target to dev2
Charles Chan19b70032019-04-17 14:20:26 -0700760 if (!batchedSubnetDev2Only.isEmpty() &&
761 batchedSubnetDev2Only.stream().anyMatch(subnet -> !subnet.isEmpty()) &&
762 nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700763 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800764 onlyDev2NextHops.put(ep.dev2, nhDev2);
Charles Chan19b70032019-04-17 14:20:26 -0700765 log.trace("getSubnets on {} only: {}", ep.dev2, batchedSubnetDev2Only);
766 for (Set<IpPrefix> prefixes : batchedSubnetDev2Only) {
767 if (!populateEcmpRoutingRulePartial(
768 targetSw,
769 ep.dev2, null,
770 onlyDev2NextHops,
771 prefixes)) {
772 return false; // abort everything and fail fast
773 }
Saurav Das261c3002017-06-13 15:35:54 -0700774 }
775 }
776 }
777 // if it gets here it has succeeded for all targets to this edge-pair
778 }
779 return true;
780 }
781
782 /**
783 * Programs targetSw in the changedRoutes for given prefixes reachable by
784 * a destination switch that is not part of an edge-pair.
785 * If no prefixes are given, the method will use configured subnets/prefixes.
786 *
787 * @param subnets a set of prefixes that need to be populated in the routing
788 * table of the target switch in the changedRoutes. Can be null,
789 * in which case all the configured prefixes belonging to the
790 * paired switches will be populated in the target switch
791 * @param changedRoutes a set of route-path changes, where each route-path is
792 * a list with its first element the src-switch (target)
793 * of the path, and the second element the dst-switch of
794 * the path.
795 * @return true if successful
796 */
797 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Das6430f412018-01-25 09:49:01 -0800798 Set<ArrayList<DeviceId>> changedRoutes,
799 Set<DeviceId> updatedDevices) {
Saurav Das261c3002017-06-13 15:35:54 -0700800 // aggregate route-path changes for each dst device
801 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
802 new HashMap<>();
803 for (ArrayList<DeviceId> route: changedRoutes) {
804 DeviceId dstSw = route.get(1);
805 ArrayList<ArrayList<DeviceId>> deviceRoutes =
806 routesBydevice.get(dstSw);
807 if (deviceRoutes == null) {
808 deviceRoutes = new ArrayList<>();
809 routesBydevice.put(dstSw, deviceRoutes);
810 }
811 deviceRoutes.add(route);
812 }
813 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
814 ArrayList<ArrayList<DeviceId>> deviceRoutes =
815 routesBydevice.get(impactedDstDevice);
816 for (ArrayList<DeviceId> route: deviceRoutes) {
817 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
818 route.get(0), route.get(1));
819 DeviceId targetSw = route.get(0);
820 DeviceId dstSw = route.get(1); // same as impactedDstDevice
821 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Das8e46aa72018-01-09 17:38:44 -0800822 if (nextHops.isEmpty()) {
Saurav Das68e1b6a2018-06-11 17:02:31 -0700823 log.debug("Could not find next hop from target:{} --> dst {} "
Saurav Das8e46aa72018-01-09 17:38:44 -0800824 + "skipping this route", targetSw, dstSw);
825 continue;
826 }
Saurav Das261c3002017-06-13 15:35:54 -0700827 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
828 nhops.put(dstSw, nextHops);
829 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
830 (subnets == null) ? Sets.newHashSet() : subnets)) {
831 return false; // abort routing and fail fast
832 }
833 log.debug("Populating flow rules from target: {} to dst: {}"
834 + " is successful", targetSw, dstSw);
835 }
836 //Only if all the flows for all impacted routes to a
837 //specific target are pushed successfully, update the
838 //ECMP graph for that target. Or else the next event
839 //would not see any changes in the ECMP graphs.
840 //In another case, the target switch has gone away, so
841 //routes can't be installed. In that case, the current map
842 //is updated here, without any flows being pushed.
843 currentEcmpSpgMap.put(impactedDstDevice,
844 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Das6430f412018-01-25 09:49:01 -0800845 updatedDevices.add(impactedDstDevice);
Saurav Das261c3002017-06-13 15:35:54 -0700846 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
847 }
848 return true;
849 }
850
851 /**
852 * Populate ECMP rules for subnets from target to destination via nexthops.
853 *
854 * @param targetSw Device ID of target switch in which rules will be programmed
855 * @param destSw1 Device ID of final destination switch to which the rules will forward
856 * @param destSw2 Device ID of paired destination switch to which the rules will forward
857 * A null deviceId indicates packets should only be sent to destSw1
Saurav Das97241862018-02-14 14:14:54 -0800858 * @param nextHops Map of a set of next hops per destSw
Saurav Das261c3002017-06-13 15:35:54 -0700859 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
860 * @return true if it succeeds in populating rules
861 */ // refactor
862 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
863 DeviceId destSw1,
864 DeviceId destSw2,
865 Map<DeviceId, Set<DeviceId>> nextHops,
866 Set<IpPrefix> subnets) {
867 boolean result;
868 // If both target switch and dest switch are edge routers, then set IP
869 // rule for both subnet and router IP.
870 boolean targetIsEdge;
871 boolean dest1IsEdge;
872 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
873 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
874
875 try {
876 targetIsEdge = config.isEdgeDevice(targetSw);
877 dest1IsEdge = config.isEdgeDevice(destSw1);
878 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
879 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
880 if (destSw2 != null) {
881 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
882 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
883 }
884 } catch (DeviceConfigNotFoundException e) {
885 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Das62ae6792017-05-15 15:34:25 -0700886 return false;
887 }
Saurav Das261c3002017-06-13 15:35:54 -0700888
889 if (targetIsEdge && dest1IsEdge) {
Charles Chan19b70032019-04-17 14:20:26 -0700890 List<Set<IpPrefix>> batchedSubnets;
891 if (subnets != null && !subnets.isEmpty()) {
892 batchedSubnets = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
893 } else {
894 batchedSubnets = config.getBatchedSubnets(destSw1);
895 }
Saurav Das97241862018-02-14 14:14:54 -0800896 // XXX - Rethink this - ignoring routerIPs in all other switches
897 // even edge to edge switches
Saurav Das261c3002017-06-13 15:35:54 -0700898 /*subnets.add(dest1RouterIpv4.toIpPrefix());
899 if (dest1RouterIpv6 != null) {
900 subnets.add(dest1RouterIpv6.toIpPrefix());
901 }
902 if (destSw2 != null && dest2RouterIpv4 != null) {
903 subnets.add(dest2RouterIpv4.toIpPrefix());
904 if (dest2RouterIpv6 != null) {
905 subnets.add(dest2RouterIpv6.toIpPrefix());
906 }
907 }*/
Charles Chan19b70032019-04-17 14:20:26 -0700908 log.trace("getSubnets on {}: {}", destSw1, batchedSubnets);
909 for (Set<IpPrefix> prefixes : batchedSubnets) {
910 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
911 + "for subnets {}", targetSw, destSw1,
912 (destSw2 != null) ? ("& " + destSw2) : "",
913 prefixes);
914 if (!rulePopulator.populateIpRuleForSubnet(targetSw, prefixes, destSw1, destSw2, nextHops)) {
915 return false;
916 }
Saurav Das261c3002017-06-13 15:35:54 -0700917 }
Saurav Das62ae6792017-05-15 15:34:25 -0700918 }
Saurav Das261c3002017-06-13 15:35:54 -0700919
920 if (!targetIsEdge && dest1IsEdge) {
921 // MPLS rules in all non-edge target devices. These rules are for
922 // individual destinations, even if the dsts are part of edge-pairs.
923 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
924 + "all MPLS rules", targetSw, destSw1);
925 result = rulePopulator.populateMplsRule(targetSw, destSw1,
926 nextHops.get(destSw1),
927 dest1RouterIpv4);
928 if (!result) {
929 return false;
930 }
931 if (dest1RouterIpv6 != null) {
Saurav Das97241862018-02-14 14:14:54 -0800932 int v4sid = 0, v6sid = 0;
933 try {
934 v4sid = config.getIPv4SegmentId(destSw1);
935 v6sid = config.getIPv6SegmentId(destSw1);
936 } catch (DeviceConfigNotFoundException e) {
937 log.warn(e.getMessage());
938 }
939 if (v4sid != v6sid) {
940 result = rulePopulator.populateMplsRule(targetSw, destSw1,
941 nextHops.get(destSw1),
942 dest1RouterIpv6);
943 if (!result) {
944 return false;
945 }
Saurav Das261c3002017-06-13 15:35:54 -0700946 }
947 }
948 }
949
Andreas Pantelopoulosfc4bc2a2018-03-12 16:30:20 -0700950 if (!targetIsEdge && !dest1IsEdge) {
951 // MPLS rules for inter-connected spines
952 // can be merged with above if, left it here for clarity
953 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
954 + "all MPLS rules", targetSw, destSw1);
955
956 result = rulePopulator.populateMplsRule(targetSw, destSw1,
957 nextHops.get(destSw1),
958 dest1RouterIpv4);
959 if (!result) {
960 return false;
961 }
962
963 if (dest1RouterIpv6 != null) {
964 int v4sid = 0, v6sid = 0;
965 try {
966 v4sid = config.getIPv4SegmentId(destSw1);
967 v6sid = config.getIPv6SegmentId(destSw1);
968 } catch (DeviceConfigNotFoundException e) {
969 log.warn(e.getMessage());
970 }
971 if (v4sid != v6sid) {
972 result = rulePopulator.populateMplsRule(targetSw, destSw1,
973 nextHops.get(destSw1),
974 dest1RouterIpv6);
975 if (!result) {
976 return false;
977 }
978 }
979 }
980 }
981
Saurav Das261c3002017-06-13 15:35:54 -0700982 // To save on ECMP groups
983 // avoid MPLS rules in non-edge-devices to non-edge-devices
984 // avoid MPLS transit rules in edge-devices
985 // avoid loopback IP rules in edge-devices to non-edge-devices
986 return true;
Saurav Das62ae6792017-05-15 15:34:25 -0700987 }
988
989 /**
990 * Processes a set a route-path changes by editing hash groups.
991 *
992 * @param routeChanges a set of route-path changes, where each route-path is
993 * a list with its first element the src-switch of the path
994 * and the second element the dst-switch of the path.
995 * @param linkOrSwitchFailed true if the route changes are for a failed
996 * switch or linkDown event
997 * @param failedSwitch the switchId if the route changes are for a failed switch,
998 * otherwise null
999 */
1000 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
1001 boolean linkOrSwitchFailed,
1002 DeviceId failedSwitch) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001003 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
1004 // first, ensure each routeChanges entry has two elements
Saurav Das62ae6792017-05-15 15:34:25 -07001005 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001006 if (route.size() == 1) {
1007 // route-path changes are from everyone else to this switch
1008 DeviceId dstSw = route.get(0);
1009 srManager.deviceService.getAvailableDevices().forEach(sw -> {
1010 if (!sw.id().equals(dstSw)) {
1011 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
1012 }
1013 });
1014 } else {
1015 changedRoutes.add(route);
Saurav Das62ae6792017-05-15 15:34:25 -07001016 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001017 }
Saurav Das6430f412018-01-25 09:49:01 -08001018 boolean someFailed = false;
1019 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001020 for (ArrayList<DeviceId> route : changedRoutes) {
1021 DeviceId targetSw = route.get(0);
1022 DeviceId dstSw = route.get(1);
Saurav Das62ae6792017-05-15 15:34:25 -07001023 if (linkOrSwitchFailed) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001024 boolean success = fixHashGroupsForRoute(route, true);
Saurav Das62ae6792017-05-15 15:34:25 -07001025 // it's possible that we cannot fix hash groups for a route
1026 // if the target switch has failed. Nevertheless the ecmp graph
1027 // for the impacted switch must still be updated.
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001028 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001029 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1030 currentEcmpSpgMap.remove(targetSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001031 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Das62ae6792017-05-15 15:34:25 -07001032 + "target:{}", dstSw, targetSw);
Saurav Das6430f412018-01-25 09:49:01 -08001033 updatedDevices.add(targetSw);
1034 updatedDevices.add(dstSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001035 continue;
Saurav Das62ae6792017-05-15 15:34:25 -07001036 }
1037 //linkfailed - update both sides
Saurav Das62ae6792017-05-15 15:34:25 -07001038 if (success) {
1039 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001040 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Das6430f412018-01-25 09:49:01 -08001041 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1042 + " or switchdown", dstSw, targetSw);
1043 updatedDevices.add(targetSw);
1044 updatedDevices.add(dstSw);
1045 } else {
1046 someFailed = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001047 }
1048 } else {
1049 //linkup of seen before link
1050 boolean success = fixHashGroupsForRoute(route, false);
1051 if (success) {
1052 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1053 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1054 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Das62ae6792017-05-15 15:34:25 -07001055 targetSw, dstSw);
Saurav Das6430f412018-01-25 09:49:01 -08001056 updatedDevices.add(targetSw);
1057 updatedDevices.add(dstSw);
1058 } else {
1059 someFailed = true;
Saurav Das62ae6792017-05-15 15:34:25 -07001060 }
1061 }
1062 }
Saurav Das6430f412018-01-25 09:49:01 -08001063 if (!someFailed) {
1064 // here is where we update all devices not touched by this instance
1065 updatedEcmpSpgMap.keySet().stream()
1066 .filter(devId -> !updatedDevices.contains(devId))
1067 .forEach(devId -> {
1068 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1069 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1070 });
1071 }
Saurav Das62ae6792017-05-15 15:34:25 -07001072 }
1073
1074 /**
1075 * Edits hash groups in the src-switch (targetSw) of a route-path by
1076 * calling the groupHandler to either add or remove buckets in an existing
1077 * hash group.
1078 *
1079 * @param route a single list representing a route-path where the first element
1080 * is the src-switch (targetSw) of the route-path and the
1081 * second element is the dst-switch
1082 * @param revoke true if buckets in the hash-groups need to be removed;
1083 * false if buckets in the hash-groups need to be added
1084 * @return true if the hash group editing is successful
1085 */
1086 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1087 boolean revoke) {
1088 DeviceId targetSw = route.get(0);
1089 if (route.size() < 2) {
1090 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1091 return false;
1092 }
1093 DeviceId destSw = route.get(1);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001094 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001095 targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001096 // figure out the new next hops at the targetSw towards the destSw
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001097 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001098 // call group handler to change hash group at targetSw
1099 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1100 if (grpHandler == null) {
1101 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1102 + " {} hash group buckets for route:{} ", targetSw,
1103 (revoke) ? "revoke" : "repopulate", route);
1104 return false;
1105 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001106 log.debug("{} hash-groups buckets For Route {} -> {} to new next-hops {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001107 (revoke) ? "revoke" : "repopulating",
1108 targetSw, destSw, nextHops);
1109 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1110 destSw, true)
1111 : grpHandler.fixHashGroups(targetSw, nextHops,
1112 destSw, false);
1113 }
1114
1115 /**
Saurav Das261c3002017-06-13 15:35:54 -07001116 * Start the flow rule population process if it was never started. The
1117 * process finishes successfully when all flow rules are set and stops with
1118 * ABORTED status when any groups required for flows is not set yet.
Saurav Das62ae6792017-05-15 15:34:25 -07001119 */
Saurav Das261c3002017-06-13 15:35:54 -07001120 public void startPopulationProcess() {
1121 statusLock.lock();
1122 try {
1123 if (populationStatus == Status.IDLE
1124 || populationStatus == Status.SUCCEEDED
1125 || populationStatus == Status.ABORTED) {
1126 populateAllRoutingRules();
sangho28d0b6d2015-05-07 13:30:57 -07001127 } else {
Saurav Das261c3002017-06-13 15:35:54 -07001128 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1129 populationStatus);
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001130 }
Saurav Das261c3002017-06-13 15:35:54 -07001131 } finally {
1132 statusLock.unlock();
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001133 }
sanghofb7c7292015-04-13 15:15:58 -07001134 }
1135
Saurav Dasb149be12016-06-07 10:08:06 -07001136 /**
Saurav Das261c3002017-06-13 15:35:54 -07001137 * Revoke rules of given subnet in all edge switches.
1138 *
1139 * @param subnets subnet being removed
1140 * @return true if succeed
1141 */
1142 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1143 statusLock.lock();
1144 try {
Charles Chand66d6712018-03-29 16:03:41 -07001145 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1146 .map(Device::id)
1147 .filter(this::shouldProgram)
1148 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das261c3002017-06-13 15:35:54 -07001149 } finally {
1150 statusLock.unlock();
1151 }
1152 }
1153
1154 /**
Charles Chan910be6a2017-08-23 14:46:43 -07001155 * Populates IP rules for a route that has direct connection to the switch
1156 * if the current instance is the master of the switch.
1157 *
1158 * @param deviceId device ID of the device that next hop attaches to
1159 * @param prefix IP prefix of the route
1160 * @param hostMac MAC address of the next hop
1161 * @param hostVlanId Vlan ID of the nexthop
1162 * @param outPort port where the next hop attaches to
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001163 * @param directHost host is of type direct or indirect
Charles Chan910be6a2017-08-23 14:46:43 -07001164 */
1165 void populateRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001166 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chand66d6712018-03-29 16:03:41 -07001167 if (shouldProgram(deviceId)) {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001168 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan910be6a2017-08-23 14:46:43 -07001169 }
1170 }
1171
1172 /**
1173 * Removes IP rules for a route when the next hop is gone.
1174 * if the current instance is the master of the switch.
1175 *
1176 * @param deviceId device ID of the device that next hop attaches to
1177 * @param prefix IP prefix of the route
1178 * @param hostMac MAC address of the next hop
1179 * @param hostVlanId Vlan ID of the nexthop
1180 * @param outPort port that next hop attaches to
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001181 * @param directHost host is of type direct or indirect
Charles Chan910be6a2017-08-23 14:46:43 -07001182 */
1183 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001184 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chand66d6712018-03-29 16:03:41 -07001185 if (shouldProgram(deviceId)) {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001186 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan910be6a2017-08-23 14:46:43 -07001187 }
1188 }
1189
Charles Chand66d6712018-03-29 16:03:41 -07001190 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1191 if (shouldProgram(deviceId)) {
1192 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1193 }
1194 }
1195
1196 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1197 if (shouldProgram(deviceId)) {
1198 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1199 }
1200 }
1201
1202 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1203 VlanId vlanId, boolean popVlan, boolean install) {
1204 if (shouldProgram(deviceId)) {
1205 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1206 }
1207 }
1208
1209 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1210 VlanId vlanId, boolean popVlan, boolean install) {
1211 if (shouldProgram(deviceId)) {
1212 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1213 vlanId, popVlan, install);
1214 }
1215 }
1216
Charles Chan910be6a2017-08-23 14:46:43 -07001217 /**
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -07001218 * Populates IP rules for a route when the next hop is double-tagged.
1219 *
1220 * @param deviceId device ID that next hop attaches to
1221 * @param prefix IP prefix of the route
1222 * @param hostMac MAC address of the next hop
1223 * @param innerVlan Inner Vlan ID of the next hop
1224 * @param outerVlan Outer Vlan ID of the next hop
1225 * @param outerTpid Outer TPID of the next hop
1226 * @param outPort port that the next hop attaches to
1227 */
1228 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1229 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1230 if (srManager.mastershipService.isLocalMaster(deviceId)) {
Charles Chan61c086d2019-07-26 17:46:15 -07001231 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1232 deviceId, prefix, hostMac, innerVlan, outerVlan, outerTpid, outPort);
1233 srManager.routingRulePopulator.processDoubleTaggedFilter(
1234 deviceId, outPort, outerVlan, innerVlan, true);
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -07001235 }
1236 }
1237
1238 /**
1239 * Revokes IP rules for a route when the next hop is double-tagged.
1240 *
1241 * @param deviceId device ID that next hop attaches to
1242 * @param prefix IP prefix of the route
1243 * @param hostMac MAC address of the next hop
1244 * @param innerVlan Inner Vlan ID of the next hop
1245 * @param outerVlan Outer Vlan ID of the next hop
1246 * @param outerTpid Outer TPID of the next hop
1247 * @param outPort port that the next hop attaches to
1248 */
1249 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1250 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1251 // Revoke route either if this node have the mastership (when device is available) or
1252 // if this node is the leader (even when device is unavailable)
1253 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1254 if (srManager.deviceService.isAvailable(deviceId)) {
1255 // Master node will revoke specified rule.
1256 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1257 return;
1258 }
1259
1260 // isLocalMaster will return false when the device is unavailable.
1261 // Verify if this node is the leader in that case.
1262 NodeId leader = srManager.leadershipService.runForLeadership(
1263 deviceId.toString()).leaderNodeId();
1264 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1265 // Leader node will revoke specified rule.
1266 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1267 return;
1268 }
1269 }
1270
Charles Chan61c086d2019-07-26 17:46:15 -07001271 srManager.routingRulePopulator.revokeDoubleTaggedRoute(deviceId, prefix, hostMac,
1272 innerVlan, outerVlan, outerTpid, outPort);
1273 srManager.routingRulePopulator.processDoubleTaggedFilter(deviceId, outPort, outerVlan, innerVlan, false);
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -07001274 }
1275
1276
1277 /**
Saurav Das261c3002017-06-13 15:35:54 -07001278 * Remove ECMP graph entry for the given device. Typically called when
1279 * device is no longer available.
1280 *
1281 * @param deviceId the device for which graphs need to be purged
1282 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001283 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das6430f412018-01-25 09:49:01 -08001284 statusLock.lock();
1285 try {
Saurav Das6430f412018-01-25 09:49:01 -08001286 if (populationStatus == Status.STARTED) {
1287 log.warn("Previous rule population is not finished. Cannot"
1288 + " proceeed with purgeEcmpGraph for {}", deviceId);
1289 return;
1290 }
1291 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1292 currentEcmpSpgMap.remove(deviceId);
1293 if (updatedEcmpSpgMap != null) {
1294 updatedEcmpSpgMap.remove(deviceId);
1295 }
1296 } finally {
1297 statusLock.unlock();
Saurav Das261c3002017-06-13 15:35:54 -07001298 }
1299 }
1300
Saurav Das00e553b2018-04-21 17:19:48 -07001301 /**
1302 * Attempts a full reroute of route-paths if topology has changed relatively
1303 * close to a mastership change event. Does not do a reroute if mastership
1304 * change is due to reasons other than a ONOS cluster event - for example a
1305 * call to balance-masters, or a switch up/down event.
1306 *
1307 * @param devId the device identifier for which mastership has changed
1308 * @param me the mastership event
1309 */
1310 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1311 // give small delay to absorb mastership events that are caused by
1312 // device that has disconnected from cluster
Saurav Das49368392018-04-23 18:42:12 -07001313 executorServiceMstChg.schedule(new MasterChange(devId, me),
1314 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das00e553b2018-04-21 17:19:48 -07001315 }
1316
1317 protected final class MasterChange implements Runnable {
1318 private DeviceId devId;
1319 private MastershipEvent me;
1320 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1321 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
Saurav Dasec683dc2018-04-27 18:42:30 -07001322 private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
Saurav Das68e1b6a2018-06-11 17:02:31 -07001323 private static final long FULL_REROUTE_THRESHOLD = 10000; // ms
Saurav Das00e553b2018-04-21 17:19:48 -07001324
1325 MasterChange(DeviceId devId, MastershipEvent me) {
1326 this.devId = devId;
1327 this.me = me;
1328 }
1329
1330 @Override
1331 public void run() {
1332 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1333 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1334
1335 // ignore event for lost switch if cluster event hasn't happened -
1336 // device down event will handle it
1337 if ((me.roleInfo().master() == null
1338 || !srManager.deviceService.isAvailable(devId))
1339 && !clusterEvent) {
1340 log.debug("Full reroute not required for lost device: {}/{} "
1341 + "clusterEvent/timeSince: {}/{}",
1342 devId, me.roleInfo(), clusterEvent, lce);
1343 return;
1344 }
1345
1346 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1347 long lde = Instant.now().toEpochMilli() - update;
1348 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1349
1350 // ignore event for recently connected switch if cluster event hasn't
1351 // happened - link up events will handle it
1352 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1353 && !clusterEvent) {
1354 log.debug("Full reroute not required for recently available"
1355 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1356 + "clusterEvent/timeSince: {}/{}",
1357 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1358 return;
1359 }
1360
Saurav Dasec683dc2018-04-27 18:42:30 -07001361 long lepe = Instant.now().toEpochMilli()
1362 - srManager.lastEdgePortEvent.toEpochMilli();
1363 boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
1364
Saurav Das00e553b2018-04-21 17:19:48 -07001365 // if it gets here, then mastership change is likely due to onos
1366 // instance failure, or network partition in onos cluster
1367 // normally a mastership change like this does not require re-programming
1368 // but if topology changes happen at the same time then we may miss events
1369 if (!isRoutingStable() && clusterEvent) {
Saurav Dasec683dc2018-04-27 18:42:30 -07001370 log.warn("Mastership changed for dev: {}/{} while programming route-paths "
Saurav Das00e553b2018-04-21 17:19:48 -07001371 + "due to clusterEvent {} ms ago .. attempting full reroute",
1372 devId, me.roleInfo(), lce);
1373 if (srManager.mastershipService.isLocalMaster(devId)) {
1374 // old master could have died when populating filters
1375 populatePortAddressingRules(devId);
1376 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001377 // old master could have died when creating groups
Saurav Das00e553b2018-04-21 17:19:48 -07001378 // XXX right now we have no fine-grained way to only make changes
Saurav Das68e1b6a2018-06-11 17:02:31 -07001379 // for the route paths affected by this device. Thus we do a
1380 // full reroute after purging all hash groups. We also try to do
1381 // it only once, irrespective of the number of devices
1382 // that changed mastership when their master instance died.
1383 long lfrr = Instant.now().toEpochMilli() - lastFullReroute.toEpochMilli();
1384 boolean doFullReroute = lfrr > FULL_REROUTE_THRESHOLD;
1385 if (doFullReroute) {
1386 lastFullReroute = Instant.now();
1387 for (Device dev : srManager.deviceService.getDevices()) {
1388 if (shouldProgram(dev.id())) {
1389 srManager.purgeHashedNextObjectiveStore(dev.id());
1390 }
1391 }
1392 // give small delay to ensure entire store is purged
1393 executorServiceFRR.schedule(new FullRerouteAfterPurge(),
1394 PURGE_DELAY,
1395 TimeUnit.MILLISECONDS);
1396 } else {
1397 log.warn("Full reroute attempted {} ms ago .. skipping", lfrr);
1398 }
Saurav Dasec683dc2018-04-27 18:42:30 -07001399
1400 } else if (edgePortEvent && clusterEvent) {
1401 log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
1402 + "while edge-port event happened {} ms ago "
1403 + " .. reprogramming all edge-ports",
1404 devId, me.roleInfo(), lce, lepe);
1405 if (shouldProgram(devId)) {
1406 srManager.deviceService.getPorts(devId).stream()
1407 .filter(p -> srManager.interfaceService
1408 .isConfigured(new ConnectPoint(devId, p.number())))
1409 .forEach(p -> srManager.processPortUpdated(devId, p));
1410 }
1411
Saurav Das00e553b2018-04-21 17:19:48 -07001412 } else {
1413 log.debug("Stable route-paths .. full reroute not attempted for "
1414 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1415 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1416 deviceEvent, lde, clusterEvent, lce);
1417 }
1418 }
1419 }
1420
Saurav Das68e1b6a2018-06-11 17:02:31 -07001421 /**
1422 * Performs a full reroute of routing rules in all the switches. Assumes
1423 * caller has purged hash groups from the nextObjective store, otherwise
1424 * re-uses ones available in the store.
1425 */
1426 protected final class FullRerouteAfterPurge implements Runnable {
1427 @Override
1428 public void run() {
1429 populateAllRoutingRules();
1430 }
1431 }
1432
1433
Saurav Das261c3002017-06-13 15:35:54 -07001434 //////////////////////////////////////
1435 // Routing helper methods and classes
1436 //////////////////////////////////////
1437
1438 /**
Saurav Das68e1b6a2018-06-11 17:02:31 -07001439 * Computes set of affected routes due to failed link. Assumes previous ecmp
1440 * shortest-path graph exists for a switch in order to compute affected
1441 * routes. If such a graph does not exist, the method returns null.
Saurav Dasb149be12016-06-07 10:08:06 -07001442 *
1443 * @param linkFail the failed link
1444 * @return the set of affected routes which may be empty if no routes were
Saurav Das68e1b6a2018-06-11 17:02:31 -07001445 * affected
Saurav Dasb149be12016-06-07 10:08:06 -07001446 */
sanghofb7c7292015-04-13 15:15:58 -07001447 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sanghofb7c7292015-04-13 15:15:58 -07001448 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1449
1450 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001451 log.debug("Computing the impacted routes for device {} due to link fail",
1452 sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001453 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001454 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001455 continue;
1456 }
Charles Chand66d6712018-03-29 16:03:41 -07001457 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001458 // check for mastership change since last run
1459 if (!lastProgrammed.contains(sw.id())) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001460 log.warn("New responsibility for this node to program dev:{}"
Saurav Das00e553b2018-04-21 17:19:48 -07001461 + " ... nuking current ECMPspg", sw.id());
1462 currentEcmpSpgMap.remove(sw.id());
1463 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001464 lastProgrammed.add(sw.id());
1465
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001466 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1467 if (ecmpSpg == null) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001468 log.warn("No existing ECMP graph for switch {}. Assuming "
1469 + "all route-paths have changed towards it.", rootSw);
1470 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
1471 if (targetSw.equals(rootSw)) {
1472 continue;
1473 }
1474 routes.add(Lists.newArrayList(targetSw, rootSw));
1475 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1476 }
1477 continue;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001478 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001479
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001480 if (log.isDebugEnabled()) {
1481 log.debug("Root switch: {}", rootSw);
1482 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1483 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1484 }
1485 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1486 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1487 // figure out if the broken link affected any route-paths in this graph
1488 for (Integer itrIdx : switchVia.keySet()) {
1489 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1490 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1491 switchVia.get(itrIdx);
1492 for (DeviceId targetSw : swViaMap.keySet()) {
1493 log.trace("TargetSwitch {} --> RootSwitch {}",
1494 targetSw, rootSw);
Saurav Dasb149be12016-06-07 10:08:06 -07001495 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1496 log.trace(" Via:");
Pier Ventreadb4ae62016-11-23 09:57:42 -08001497 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb149be12016-06-07 10:08:06 -07001498 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001499 Set<ArrayList<DeviceId>> subLinks =
1500 computeLinks(targetSw, rootSw, swViaMap);
1501 for (ArrayList<DeviceId> alink: subLinks) {
1502 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1503 alink.get(1).equals(linkFail.dst().deviceId()))
1504 ||
1505 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1506 alink.get(1).equals(linkFail.src().deviceId()))) {
1507 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1508 ArrayList<DeviceId> aRoute = new ArrayList<>();
1509 aRoute.add(targetSw); // switch with rules to populate
1510 aRoute.add(rootSw); // towards this destination
1511 routes.add(aRoute);
1512 break;
1513 }
sanghofb7c7292015-04-13 15:15:58 -07001514 }
1515 }
1516 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001517
sanghofb7c7292015-04-13 15:15:58 -07001518 }
sangho28d0b6d2015-05-07 13:30:57 -07001519
sanghofb7c7292015-04-13 15:15:58 -07001520 }
sanghofb7c7292015-04-13 15:15:58 -07001521 return routes;
1522 }
1523
Saurav Das1b391d52016-11-29 14:27:25 -08001524 /**
1525 * Computes set of affected routes due to new links or failed switches.
1526 *
Saurav Dasdc7f2752018-03-18 21:28:15 -07001527 * @param failedSwitch deviceId of failed switch if any
Saurav Das1b391d52016-11-29 14:27:25 -08001528 * @return the set of affected routes which may be empty if no routes were
1529 * affected
1530 */
Saurav Dascea556f2018-03-05 14:37:16 -08001531 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das261c3002017-06-13 15:35:54 -07001532 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das1b391d52016-11-29 14:27:25 -08001533 ImmutableSet.builder();
sanghofb7c7292015-04-13 15:15:58 -07001534
1535 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das261c3002017-06-13 15:35:54 -07001536 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001537 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001538 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001539 continue;
1540 }
Charles Chand66d6712018-03-29 16:03:41 -07001541 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das261c3002017-06-13 15:35:54 -07001542 if (log.isTraceEnabled()) {
1543 log.trace("Device links for dev: {}", rootSw);
1544 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1545 log.trace("{} -> {} ", link.src().deviceId(),
1546 link.dst().deviceId());
1547 }
Saurav Dasb149be12016-06-07 10:08:06 -07001548 }
Saurav Das00e553b2018-04-21 17:19:48 -07001549 // check for mastership change since last run
1550 if (!lastProgrammed.contains(sw.id())) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001551 log.warn("New responsibility for this node to program dev:{}"
Saurav Das00e553b2018-04-21 17:19:48 -07001552 + " ... nuking current ECMPspg", sw.id());
1553 currentEcmpSpgMap.remove(sw.id());
1554 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001555 lastProgrammed.add(sw.id());
Saurav Das261c3002017-06-13 15:35:54 -07001556 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1557 if (currEcmpSpg == null) {
1558 log.debug("No existing ECMP graph for device {}.. adding self as "
1559 + "changed route", rootSw);
1560 changedRtBldr.add(Lists.newArrayList(rootSw));
1561 continue;
1562 }
1563 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Dasdebcf882018-04-06 20:16:01 -07001564 if (newEcmpSpg == null) {
1565 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1566 continue;
1567 }
Saurav Das261c3002017-06-13 15:35:54 -07001568 if (log.isDebugEnabled()) {
1569 log.debug("Root switch: {}", rootSw);
1570 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1571 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1572 }
1573 // first use the updated/new map to compare to current/existing map
1574 // as new links may have come up
1575 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1576 // then use the current/existing map to compare to updated/new map
1577 // as switch may have been removed
1578 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho28d0b6d2015-05-07 13:30:57 -07001579 }
Saurav Das1b391d52016-11-29 14:27:25 -08001580 }
sanghofb7c7292015-04-13 15:15:58 -07001581
Saurav Dascea556f2018-03-05 14:37:16 -08001582 // handle clearing state for a failed switch in case the switch does
1583 // not have a pair, or the pair is not available
1584 if (failedSwitch != null) {
Charles Chan6dbcd252018-04-02 11:46:38 -07001585 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1586 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dascea556f2018-03-05 14:37:16 -08001587 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1588 srManager.deviceService.getDevices().forEach(dev -> {
1589 if (!dev.id().equals(failedSwitch) &&
1590 srManager.mastershipService.isLocalMaster(dev.id())) {
1591 log.debug(" : {}", dev.id());
1592 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1593 }
1594 });
1595 }
1596 }
1597
Saurav Das261c3002017-06-13 15:35:54 -07001598 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das1b391d52016-11-29 14:27:25 -08001599 for (ArrayList<DeviceId> route: changedRoutes) {
1600 log.debug("Route changes Target -> Root");
1601 if (route.size() == 1) {
1602 log.debug(" : all -> {}", route.get(0));
1603 } else {
1604 log.debug(" : {} -> {}", route.get(0), route.get(1));
1605 }
1606 }
1607 return changedRoutes;
1608 }
1609
1610 /**
1611 * For the root switch, searches all the target nodes reachable in the base
1612 * graph, and compares paths to the ones in the comp graph.
1613 *
1614 * @param base the graph that is indexed for all reachable target nodes
1615 * from the root node
1616 * @param comp the graph that the base graph is compared to
1617 * @param rootSw both ecmp graphs are calculated for the root node
1618 * @return all the routes that have changed in the base graph
1619 */
1620 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1621 EcmpShortestPathGraph comp,
1622 DeviceId rootSw) {
1623 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1624 ImmutableSet.builder();
1625 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1626 base.getAllLearnedSwitchesAndVia();
1627 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1628 comp.getAllLearnedSwitchesAndVia();
1629 for (Integer itrIdx : baseMap.keySet()) {
1630 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1631 baseMap.get(itrIdx);
1632 for (DeviceId targetSw : baseViaMap.keySet()) {
1633 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1634 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1635 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001636 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das1b391d52016-11-29 14:27:25 -08001637 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das261c3002017-06-13 15:35:54 -07001638 route.add(targetSw); // switch with rules to populate
1639 route.add(rootSw); // towards this destination
Saurav Das1b391d52016-11-29 14:27:25 -08001640 changedRoutesBuilder.add(route);
sanghofb7c7292015-04-13 15:15:58 -07001641 }
1642 }
sangho28d0b6d2015-05-07 13:30:57 -07001643 }
Saurav Das1b391d52016-11-29 14:27:25 -08001644 return changedRoutesBuilder.build();
sanghofb7c7292015-04-13 15:15:58 -07001645 }
1646
Saurav Das261c3002017-06-13 15:35:54 -07001647 /**
1648 * Returns the ECMP paths traversed to reach the target switch.
1649 *
1650 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1651 * @param targetSw the switch to reach from the root switch
1652 * @return the nodes traversed on ECMP paths to the target switch
1653 */
sanghofb7c7292015-04-13 15:15:58 -07001654 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das1b391d52016-11-29 14:27:25 -08001655 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sanghofb7c7292015-04-13 15:15:58 -07001656 for (Integer itrIdx : switchVia.keySet()) {
1657 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1658 switchVia.get(itrIdx);
Saurav Das1b391d52016-11-29 14:27:25 -08001659 if (swViaMap.get(targetSw) == null) {
sanghofb7c7292015-04-13 15:15:58 -07001660 continue;
1661 } else {
Saurav Das1b391d52016-11-29 14:27:25 -08001662 return swViaMap.get(targetSw);
sanghofb7c7292015-04-13 15:15:58 -07001663 }
1664 }
1665
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001666 return null;
sanghofb7c7292015-04-13 15:15:58 -07001667 }
1668
Saurav Das261c3002017-06-13 15:35:54 -07001669 /**
1670 * Utility method to break down a path from src to dst device into a collection
1671 * of links.
1672 *
1673 * @param src src device of the path
1674 * @param dst dst device of the path
1675 * @param viaMap path taken from src to dst device
1676 * @return collection of links in the path
1677 */
sanghofb7c7292015-04-13 15:15:58 -07001678 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1679 DeviceId dst,
1680 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1681 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1682 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1683 DeviceId linkSrc = src;
1684 DeviceId linkDst = dst;
1685 for (DeviceId viaDevice: via) {
1686 ArrayList<DeviceId> link = new ArrayList<>();
1687 linkDst = viaDevice;
1688 link.add(linkSrc);
1689 link.add(linkDst);
1690 subLinks.add(link);
1691 linkSrc = viaDevice;
1692 }
1693 ArrayList<DeviceId> link = new ArrayList<>();
1694 link.add(linkSrc);
1695 link.add(dst);
1696 subLinks.add(link);
1697 }
1698
1699 return subLinks;
1700 }
1701
Charles Chanc22cef32016-04-29 14:38:22 -07001702 /**
Charles Chand66d6712018-03-29 16:03:41 -07001703 * Determines whether this controller instance should program the
Saurav Das261c3002017-06-13 15:35:54 -07001704 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chand66d6712018-03-29 16:03:41 -07001705 * <p>
1706 * Once an instance is elected, it will be the only instance responsible for programming
1707 * both devices in the pair until it goes down.
Charles Chanc22cef32016-04-29 14:38:22 -07001708 *
Saurav Das261c3002017-06-13 15:35:54 -07001709 * @param deviceId device identifier to consider for routing
Charles Chand66d6712018-03-29 16:03:41 -07001710 * @return true if current instance should handle the routing for given device
Charles Chanc22cef32016-04-29 14:38:22 -07001711 */
Charles Chand66d6712018-03-29 16:03:41 -07001712 boolean shouldProgram(DeviceId deviceId) {
Charles Chanfbcb8812018-04-18 18:41:05 -07001713 Boolean cached = shouldProgramCache.get(deviceId);
1714 if (cached != null) {
Saurav Das00e553b2018-04-21 17:19:48 -07001715 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chanfbcb8812018-04-18 18:41:05 -07001716 return cached;
1717 }
1718
Charles Chand66d6712018-03-29 16:03:41 -07001719 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sangho80f11cb2015-04-01 13:05:26 -07001720
Charles Chand66d6712018-03-29 16:03:41 -07001721 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1722 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1723 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
Saurav Das68e1b6a2018-06-11 17:02:31 -07001724 log.debug("Evaluate shouldProgram {}/pair={}. currentNodeId={}, master={}, pairMaster={}",
Charles Chand66d6712018-03-29 16:03:41 -07001725 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1726
1727 // No pair device configured. Only handle when current instance is the master of the device
1728 if (!pairDeviceId.isPresent()) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001729 log.debug("No pair device. currentNodeId={}, master={}", currentNodeId, masterNodeId);
Charles Chand66d6712018-03-29 16:03:41 -07001730 return currentNodeId.equals(masterNodeId);
sangho80f11cb2015-04-01 13:05:26 -07001731 }
Charles Chand66d6712018-03-29 16:03:41 -07001732
1733 // Should not handle if current instance is not the master of either switch
1734 if (!currentNodeId.equals(masterNodeId) &&
1735 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001736 log.debug("Current nodeId {} is neither the master of target device {} nor pair device {}",
Charles Chand66d6712018-03-29 16:03:41 -07001737 currentNodeId, deviceId, pairDeviceId);
1738 return false;
1739 }
1740
1741 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1742
1743 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1744 if (v == null) {
1745 // There is no value in the map. Elect a node
1746 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1747 } else {
1748 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1749 // Use the node in the map if it is still alive and is a master of any of the two switches
1750 return v;
1751 } else {
1752 // Previously elected node is no longer the master of either switch. Re-elect a node.
1753 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1754 }
1755 }
1756 }));
1757
1758 if (king != null) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001759 log.debug("{} is king, should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001760 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chand66d6712018-03-29 16:03:41 -07001761 return king.equals(currentNodeId);
1762 } else {
1763 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001764 shouldProgramCache.remove(deviceId);
Charles Chand66d6712018-03-29 16:03:41 -07001765 return false;
1766 }
1767 }
1768
1769 /**
1770 * Elects a node who should take responsibility of programming devices.
1771 * @param nodeIds list of candidate node ID
1772 *
1773 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1774 */
1775 private NodeId elect(List<NodeId> nodeIds) {
1776 // Remove all null elements. This could happen when some device has no master
1777 nodeIds.removeAll(Collections.singleton(null));
1778 nodeIds.sort(null);
1779 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1780 }
1781
Charles Chanfbcb8812018-04-18 18:41:05 -07001782 void invalidateShouldProgramCache(DeviceId deviceId) {
1783 shouldProgramCache.remove(deviceId);
1784 }
1785
Charles Chand66d6712018-03-29 16:03:41 -07001786 /**
1787 * Returns a set of device ID, containing given device and its pair device if exist.
1788 *
1789 * @param deviceId Device ID
1790 * @return a set of device ID, containing given device and its pair device if exist.
1791 */
1792 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1793 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1794 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1795 return ret;
sangho80f11cb2015-04-01 13:05:26 -07001796 }
1797
Charles Chanc22cef32016-04-29 14:38:22 -07001798 /**
Saurav Das261c3002017-06-13 15:35:54 -07001799 * Returns the set of deviceIds which are the next hops from the targetSw
1800 * to the dstSw according to the latest ECMP spg.
1801 *
1802 * @param targetSw the switch for which the next-hops are desired
1803 * @param dstSw the switch to which the next-hops lead to from the targetSw
1804 * @return set of next hop deviceIds, could be empty if no next hops are found
1805 */
1806 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1807 boolean targetIsEdge = false;
1808 try {
1809 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1810 } catch (DeviceConfigNotFoundException e) {
1811 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1812 + "continuing to getNextHops", targetSw);
1813 }
1814
1815 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1816 if (ecmpSpg == null) {
1817 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1818 return ImmutableSet.of();
1819 }
1820 HashMap<Integer,
1821 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1822 ecmpSpg.getAllLearnedSwitchesAndVia();
1823 for (Integer itrIdx : switchVia.keySet()) {
1824 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1825 switchVia.get(itrIdx);
1826 for (DeviceId target : swViaMap.keySet()) {
1827 if (!target.equals(targetSw)) {
1828 continue;
1829 }
Saurav Das49368392018-04-23 18:42:12 -07001830 // optimization for spines to not use leaves to get
1831 // to a spine or other leaves. Also leaves should not use other
1832 // leaves to get to the destination
1833 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Das97241862018-02-14 14:14:54 -08001834 boolean pathdevIsEdge = false;
1835 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Das49368392018-04-23 18:42:12 -07001836 log.debug("Evaluating next-hop in path: {}", via);
Saurav Das97241862018-02-14 14:14:54 -08001837 for (DeviceId pathdev : via) {
1838 try {
1839 pathdevIsEdge = srManager.deviceConfiguration
1840 .isEdgeDevice(pathdev);
1841 } catch (DeviceConfigNotFoundException e) {
1842 log.warn(e.getMessage());
1843 }
1844 if (pathdevIsEdge) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001845 log.debug("Avoiding {} hop path for targetSw:{}"
Saurav Das97241862018-02-14 14:14:54 -08001846 + " --> dstSw:{} which goes through an edge"
1847 + " device {} in path {}", itrIdx,
1848 targetSw, dstSw, pathdev, via);
1849 return ImmutableSet.of();
1850 }
1851 }
1852 }
Saurav Das261c3002017-06-13 15:35:54 -07001853 }
1854 Set<DeviceId> nextHops = new HashSet<>();
1855 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1856 if (via.isEmpty()) {
1857 // the dstSw is the next-hop from the targetSw
1858 nextHops.add(dstSw);
1859 } else {
1860 // first elem is next-hop in each ECMP path
1861 nextHops.add(via.get(0));
1862 }
1863 }
Saurav Das49368392018-04-23 18:42:12 -07001864 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
1865 dstSw, nextHops);
Saurav Das261c3002017-06-13 15:35:54 -07001866 return nextHops;
1867 }
1868 }
Saurav Das49368392018-04-23 18:42:12 -07001869 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das261c3002017-06-13 15:35:54 -07001870 return ImmutableSet.of(); //no next-hops found
1871 }
1872
Saurav Das261c3002017-06-13 15:35:54 -07001873 //////////////////////////////////////
1874 // Filtering rule creation
1875 //////////////////////////////////////
1876
1877 /**
Saurav Dasf9332192017-02-18 14:05:44 -08001878 * Populates filtering rules for port, and punting rules
1879 * for gateway IPs, loopback IPs and arp/ndp traffic.
1880 * Should only be called by the master instance for this device/port.
sangho80f11cb2015-04-01 13:05:26 -07001881 *
1882 * @param deviceId Switch ID to set the rules
1883 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001884 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das07c74602016-04-27 18:35:50 -07001885 // Although device is added, sometimes device store does not have the
1886 // ports for this device yet. It results in missing filtering rules in the
1887 // switch. We will attempt it a few times. If it still does not work,
1888 // user can manually repopulate using CLI command sr-reroute-network
Charles Chan18fa4252017-02-08 16:10:40 -08001889 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001890 if (firstRun == null) {
1891 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das07c74602016-04-27 18:35:50 -07001892 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001893 executorService.schedule(new RetryFilters(deviceId, firstRun),
1894 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sangho80f11cb2015-04-01 13:05:26 -07001895 }
1896
1897 /**
Saurav Dasd1872b02016-12-02 15:43:47 -08001898 * RetryFilters populates filtering objectives for a device and keeps retrying
1899 * till the number of ports filtered are constant for a predefined number
1900 * of attempts.
1901 */
1902 protected final class RetryFilters implements Runnable {
1903 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1904 DeviceId devId;
1905 int counter;
1906 PortFilterInfo prevRun;
1907
1908 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das07c74602016-04-27 18:35:50 -07001909 devId = deviceId;
Saurav Dasd1872b02016-12-02 15:43:47 -08001910 prevRun = previousRun;
1911 counter = 0;
Saurav Das07c74602016-04-27 18:35:50 -07001912 }
1913
1914 @Override
1915 public void run() {
Charles Chan077314e2017-06-22 14:27:17 -07001916 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chan18fa4252017-02-08 16:10:40 -08001917 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001918 boolean sameResult = prevRun.equals(thisRun);
1919 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1920 thisRun, sameResult);
Ray Milkey614352e2018-02-26 09:36:31 -08001921 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Dasf9332192017-02-18 14:05:44 -08001922 // exponentially increasing intervals for retries
1923 executorService.schedule(this,
1924 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1925 TimeUnit.MILLISECONDS);
Saurav Dasd1872b02016-12-02 15:43:47 -08001926 if (!sameResult) {
1927 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1928 }
Saurav Das07c74602016-04-27 18:35:50 -07001929 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001930 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das07c74602016-04-27 18:35:50 -07001931 }
Saurav Das07c74602016-04-27 18:35:50 -07001932 }
sangho80f11cb2015-04-01 13:05:26 -07001933}