blob: f456e6634275ace151edbfcf7d382450e30d296f [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;
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -070042import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
Charles Chand66d6712018-03-29 16:03:41 -070043import org.onosproject.store.serializers.KryoNamespaces;
44import org.onosproject.store.service.Serializer;
sangho80f11cb2015-04-01 13:05:26 -070045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -070048import java.time.Instant;
sangho80f11cb2015-04-01 13:05:26 -070049import java.util.ArrayList;
Charles Chand66d6712018-03-29 16:03:41 -070050import java.util.Collections;
sangho80f11cb2015-04-01 13:05:26 -070051import java.util.HashMap;
52import java.util.HashSet;
Saurav Das261c3002017-06-13 15:35:54 -070053import java.util.Iterator;
Charles Chand66d6712018-03-29 16:03:41 -070054import java.util.List;
Saurav Das261c3002017-06-13 15:35:54 -070055import java.util.Map;
Saurav Dasd1872b02016-12-02 15:43:47 -080056import java.util.Objects;
Charles Chan6dbcd252018-04-02 11:46:38 -070057import java.util.Optional;
sangho80f11cb2015-04-01 13:05:26 -070058import java.util.Set;
Saurav Das07c74602016-04-27 18:35:50 -070059import java.util.concurrent.ScheduledExecutorService;
60import java.util.concurrent.TimeUnit;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090061import java.util.concurrent.locks.Lock;
62import java.util.concurrent.locks.ReentrantLock;
Charles Chan19b70032019-04-17 14:20:26 -070063import java.util.stream.Collectors;
Saurav Dasdc7f2752018-03-18 21:28:15 -070064import java.util.stream.Stream;
65
Pier Ventreadb4ae62016-11-23 09:57:42 -080066import static com.google.common.base.Preconditions.checkNotNull;
67import static java.util.concurrent.Executors.newScheduledThreadPool;
68import static org.onlab.util.Tools.groupedThreads;
sangho80f11cb2015-04-01 13:05:26 -070069
Charles Chanb7f75ac2016-01-11 18:28:54 -080070/**
71 * Default routing handler that is responsible for route computing and
72 * routing rule population.
73 */
sangho80f11cb2015-04-01 13:05:26 -070074public class DefaultRoutingHandler {
Saurav Dasf9332192017-02-18 14:05:44 -080075 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey092e9e22018-02-01 13:49:47 -080076 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Dasf9332192017-02-18 14:05:44 -080077 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasfbe74572017-08-03 18:30:35 -070078 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das00e553b2018-04-21 17:19:48 -070079 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Saurav Das68e1b6a2018-06-11 17:02:31 -070080 private static final long PURGE_DELAY = 1000; // ms
Charles Chanc22cef32016-04-29 14:38:22 -070081 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sangho80f11cb2015-04-01 13:05:26 -070082
83 private SegmentRoutingManager srManager;
84 private RoutingRulePopulator rulePopulator;
Shashikanth VH0637b162015-12-11 01:32:44 +053085 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
86 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho9b169e32015-04-14 16:27:13 -070087 private DeviceConfiguration config;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090088 private final Lock statusLock = new ReentrantLock();
89 private volatile Status populationStatus;
Yuta HIGUCHIebee2f12016-07-21 16:54:33 -070090 private ScheduledExecutorService executorService
Saurav Dasd1872b02016-12-02 15:43:47 -080091 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070092 private ScheduledExecutorService executorServiceMstChg
93 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
Saurav Das68e1b6a2018-06-11 17:02:31 -070094 private ScheduledExecutorService executorServiceFRR
95 = newScheduledThreadPool(1, groupedThreads("fullRR", "fullRR-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070096
Saurav Das00e553b2018-04-21 17:19:48 -070097 private Instant lastRoutingChange = Instant.EPOCH;
Saurav Das68e1b6a2018-06-11 17:02:31 -070098 private Instant lastFullReroute = Instant.EPOCH;
sangho80f11cb2015-04-01 13:05:26 -070099
Saurav Das00e553b2018-04-21 17:19:48 -0700100 // Distributed store to keep track of ONOS instance that should program the
101 // device pair. There should be only one instance (the king) that programs the same pair.
Charles Chand66d6712018-03-29 16:03:41 -0700102 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chanfbcb8812018-04-18 18:41:05 -0700103 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chand66d6712018-03-29 16:03:41 -0700104
Saurav Das00e553b2018-04-21 17:19:48 -0700105 // Local store to keep track of all devices that this instance was responsible
106 // for programming in the last run. Helps to determine if mastership changed
107 // during a run - only relevant for programming as a result of topo change.
108 Set<DeviceId> lastProgrammed;
109
sangho80f11cb2015-04-01 13:05:26 -0700110 /**
111 * Represents the default routing population status.
112 */
113 public enum Status {
114 // population process is not started yet.
115 IDLE,
116
117 // population process started.
118 STARTED,
119
Srikanth Vavilapalli64505482015-04-21 13:04:13 -0700120 // population process was aborted due to errors, mostly for groups not
121 // found.
sangho80f11cb2015-04-01 13:05:26 -0700122 ABORTED,
123
124 // population process was finished successfully.
125 SUCCEEDED
126 }
127
128 /**
129 * Creates a DefaultRoutingHandler object.
130 *
131 * @param srManager SegmentRoutingManager object
132 */
Charles Chand66d6712018-03-29 16:03:41 -0700133 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700134 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
135 .withName("sr-should-program")
136 .withSerializer(Serializer.using(KryoNamespaces.API))
137 .withRelaxedReadConsistency()
138 .build().asJavaMap();
139 this.shouldProgramCache = Maps.newConcurrentMap();
140 update(srManager);
141 }
142
143 /**
144 * Updates a DefaultRoutingHandler object.
145 *
146 * @param srManager SegmentRoutingManager object
147 */
148 void update(SegmentRoutingManager srManager) {
sangho80f11cb2015-04-01 13:05:26 -0700149 this.srManager = srManager;
150 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho9b169e32015-04-14 16:27:13 -0700151 this.config = checkNotNull(srManager.deviceConfiguration);
sangho80f11cb2015-04-01 13:05:26 -0700152 this.populationStatus = Status.IDLE;
sanghofb7c7292015-04-13 15:15:58 -0700153 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das00e553b2018-04-21 17:19:48 -0700154 this.lastProgrammed = Sets.newConcurrentHashSet();
sangho80f11cb2015-04-01 13:05:26 -0700155 }
156
157 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700158 * Returns an immutable copy of the current ECMP shortest-path graph as
159 * computed by this controller instance.
160 *
Saurav Das261c3002017-06-13 15:35:54 -0700161 * @return immutable copy of the current ECMP graph
Saurav Das62ae6792017-05-15 15:34:25 -0700162 */
163 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
164 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
165 currentEcmpSpgMap.entrySet().forEach(entry -> {
166 if (entry.getValue() != null) {
167 builder.put(entry.getKey(), entry.getValue());
168 }
169 });
170 return builder.build();
171 }
172
Saurav Dasfbe74572017-08-03 18:30:35 -0700173 /**
174 * Acquires the lock used when making routing changes.
175 */
176 public void acquireRoutingLock() {
177 statusLock.lock();
178 }
179
180 /**
181 * Releases the lock used when making routing changes.
182 */
183 public void releaseRoutingLock() {
184 statusLock.unlock();
185 }
186
187 /**
188 * Determines if routing in the network has been stable in the last
189 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
190 * routing change timestamp.
191 *
192 * @return true if stable
193 */
194 public boolean isRoutingStable() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700195 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
196 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700197 log.trace("Routing stable since {}s", now - last);
Saurav Dasfbe74572017-08-03 18:30:35 -0700198 return (now - last) > STABLITY_THRESHOLD;
199 }
200
Saurav Das49368392018-04-23 18:42:12 -0700201 /**
202 * Gracefully shuts down the defaultRoutingHandler. Typically called when
203 * the app is deactivated
204 */
205 public void shutdown() {
206 executorService.shutdown();
207 executorServiceMstChg.shutdown();
Saurav Das68e1b6a2018-06-11 17:02:31 -0700208 executorServiceFRR.shutdown();
Saurav Das49368392018-04-23 18:42:12 -0700209 }
Saurav Dasfbe74572017-08-03 18:30:35 -0700210
Saurav Das261c3002017-06-13 15:35:54 -0700211 //////////////////////////////////////
212 // Route path handling
213 //////////////////////////////////////
214
Saurav Dase6c448a2018-01-18 12:07:33 -0800215 /* The following three methods represent the three major ways in which
216 * route-path handling is triggered in the network
Saurav Das261c3002017-06-13 15:35:54 -0700217 * a) due to configuration change
218 * b) due to route-added event
219 * c) due to change in the topology
220 */
221
Saurav Das62ae6792017-05-15 15:34:25 -0700222 /**
Saurav Das261c3002017-06-13 15:35:54 -0700223 * Populates all routing rules to all switches. Typically triggered at
224 * startup or after a configuration event.
sangho80f11cb2015-04-01 13:05:26 -0700225 */
Saurav Das62ae6792017-05-15 15:34:25 -0700226 public void populateAllRoutingRules() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700227 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900228 statusLock.lock();
229 try {
Saurav Das261c3002017-06-13 15:35:54 -0700230 if (populationStatus == Status.STARTED) {
231 log.warn("Previous rule population is not finished. Cannot"
232 + " proceed with populateAllRoutingRules");
233 return;
234 }
235
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900236 populationStatus = Status.STARTED;
237 rulePopulator.resetCounter();
Saurav Das261c3002017-06-13 15:35:54 -0700238 log.info("Starting to populate all routing rules");
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900239 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sangho80f11cb2015-04-01 13:05:26 -0700240
Saurav Das261c3002017-06-13 15:35:54 -0700241 // take a snapshot of the topology
242 updatedEcmpSpgMap = new HashMap<>();
243 Set<EdgePair> edgePairs = new HashSet<>();
244 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart61e24e12017-11-30 18:23:42 -0800245 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das261c3002017-06-13 15:35:54 -0700246 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart61e24e12017-11-30 18:23:42 -0800247 new EcmpShortestPathGraph(dstSw, srManager);
248 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700249 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
250 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700251 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700252 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
253 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
254 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700255 }
Charles Chand66d6712018-03-29 16:03:41 -0700256
257 if (!shouldProgram(dstSw)) {
Saurav Das00e553b2018-04-21 17:19:48 -0700258 lastProgrammed.remove(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900259 continue;
Saurav Das00e553b2018-04-21 17:19:48 -0700260 } else {
261 lastProgrammed.add(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900262 }
Saurav Das00e553b2018-04-21 17:19:48 -0700263 // To do a full reroute, assume all route-paths have changed
Charles Chand66d6712018-03-29 16:03:41 -0700264 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart61e24e12017-11-30 18:23:42 -0800265 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
266 if (targetSw.equals(dev)) {
Saurav Das261c3002017-06-13 15:35:54 -0700267 continue;
268 }
Jonathan Hart61e24e12017-11-30 18:23:42 -0800269 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das261c3002017-06-13 15:35:54 -0700270 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900271 }
Saurav Das261c3002017-06-13 15:35:54 -0700272 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900273
Saurav Das261c3002017-06-13 15:35:54 -0700274 if (!redoRouting(routeChanges, edgePairs, null)) {
275 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
276 populationStatus = Status.ABORTED;
277 log.warn("Failed to repopulate all routing rules.");
278 return;
sangho80f11cb2015-04-01 13:05:26 -0700279 }
280
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900281 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
282 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700283 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900284 rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700285 return;
pierdebd15c2019-04-19 20:55:53 +0200286 } catch (Exception e) {
287 log.error("populateAllRoutingRules thrown an exception: {}",
288 e.getMessage(), e);
289 populationStatus = Status.ABORTED;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900290 } finally {
291 statusLock.unlock();
sangho80f11cb2015-04-01 13:05:26 -0700292 }
sangho80f11cb2015-04-01 13:05:26 -0700293 }
294
sanghofb7c7292015-04-13 15:15:58 -0700295 /**
Saurav Das261c3002017-06-13 15:35:54 -0700296 * Populate rules from all other edge devices to the connect-point(s)
297 * specified for the given subnets.
298 *
299 * @param cpts connect point(s) of the subnets being added
300 * @param subnets subnets being added
Charles Chan910be6a2017-08-23 14:46:43 -0700301 */
302 // XXX refactor
Saurav Das261c3002017-06-13 15:35:54 -0700303 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan6db55b92017-09-11 15:21:57 -0700304 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
305 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
306 return;
307 }
308
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700309 lastRoutingChange = Instant.now();
Saurav Das261c3002017-06-13 15:35:54 -0700310 statusLock.lock();
311 try {
312 if (populationStatus == Status.STARTED) {
313 log.warn("Previous rule population is not finished. Cannot"
314 + " proceed with routing rules for added routes");
315 return;
316 }
317 populationStatus = Status.STARTED;
318 rulePopulator.resetCounter();
Charles Chan910be6a2017-08-23 14:46:43 -0700319 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
320 subnets, cpts);
Saurav Das6430f412018-01-25 09:49:01 -0800321 // In principle an update to a subnet/prefix should not require a
322 // new ECMPspg calculation as it is not a topology event. As a
323 // result, we use the current/existing ECMPspg in the updated map
324 // used by the redoRouting method.
Saurav Das6de6ffd2018-02-09 09:15:03 -0800325 if (updatedEcmpSpgMap == null) {
326 updatedEcmpSpgMap = new HashMap<>();
327 }
Saurav Das6430f412018-01-25 09:49:01 -0800328 currentEcmpSpgMap.entrySet().forEach(entry -> {
329 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase321cff2018-02-09 17:26:45 -0800330 if (log.isTraceEnabled()) {
331 log.trace("Root switch: {}", entry.getKey());
332 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Das6430f412018-01-25 09:49:01 -0800333 }
334 });
Saurav Das261c3002017-06-13 15:35:54 -0700335 Set<EdgePair> edgePairs = new HashSet<>();
336 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
337 boolean handleRouting = false;
338
339 if (cpts.size() == 2) {
340 // ensure connect points are edge-pairs
341 Iterator<ConnectPoint> iter = cpts.iterator();
342 DeviceId dev1 = iter.next().deviceId();
Charles Chan6dbcd252018-04-02 11:46:38 -0700343 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
344 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
345 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700346 } else {
347 log.warn("Connectpoints {} for subnets {} not on "
348 + "pair-devices.. aborting populateSubnet", cpts, subnets);
349 populationStatus = Status.ABORTED;
350 return;
351 }
352 for (ConnectPoint cp : cpts) {
Saurav Das6430f412018-01-25 09:49:01 -0800353 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
354 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700355 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800356 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
357 log.warn("populateSubnet: no updated graph for dev:{}"
358 + " ... creating", cp.deviceId());
359 }
Charles Chand66d6712018-03-29 16:03:41 -0700360 if (!shouldProgram(cp.deviceId())) {
Saurav Das261c3002017-06-13 15:35:54 -0700361 continue;
362 }
363 handleRouting = true;
364 }
365 } else {
366 // single connect point
367 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Das6430f412018-01-25 09:49:01 -0800368 if (updatedEcmpSpgMap.get(dstSw) == null) {
369 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700370 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800371 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
372 log.warn("populateSubnet: no updated graph for dev:{}"
373 + " ... creating", dstSw);
374 }
Charles Chand66d6712018-03-29 16:03:41 -0700375 handleRouting = shouldProgram(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700376 }
377
378 if (!handleRouting) {
379 log.debug("This instance is not handling ecmp routing to the "
380 + "connectPoint(s) {}", cpts);
381 populationStatus = Status.ABORTED;
382 return;
383 }
384
385 // if it gets here, this instance should handle routing for the
386 // connectpoint(s). Assume all route-paths have to be updated to
387 // the connectpoint(s) with the following exceptions
388 // 1. if target is non-edge no need for routing rules
389 // 2. if target is one of the connectpoints
390 for (ConnectPoint cp : cpts) {
391 DeviceId dstSw = cp.deviceId();
392 for (Device targetSw : srManager.deviceService.getDevices()) {
393 boolean isEdge = false;
394 try {
395 isEdge = config.isEdgeDevice(targetSw.id());
396 } catch (DeviceConfigNotFoundException e) {
Charles Chaneaf3c9b2018-02-16 17:20:54 -0800397 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
398 continue;
Saurav Das261c3002017-06-13 15:35:54 -0700399 }
Charles Chan6dbcd252018-04-02 11:46:38 -0700400 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700401 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chan6dbcd252018-04-02 11:46:38 -0700402 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das261c3002017-06-13 15:35:54 -0700403 continue;
404 }
405 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
406 }
407 }
408
409 if (!redoRouting(routeChanges, edgePairs, subnets)) {
410 log.debug("populateSubnet: populationStatus is ABORTED");
411 populationStatus = Status.ABORTED;
412 log.warn("Failed to repopulate the rules for subnet.");
413 return;
414 }
415
416 log.debug("populateSubnet: populationStatus is SUCCEEDED");
417 populationStatus = Status.SUCCEEDED;
418 log.info("Completed subnet population. Total # of rules pushed : {}",
419 rulePopulator.getCounter());
420 return;
421
pierdebd15c2019-04-19 20:55:53 +0200422 } catch (Exception e) {
423 log.error("populateSubnet thrown an exception: {}",
424 e.getMessage(), e);
425 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700426 } finally {
427 statusLock.unlock();
428 }
429 }
430
431 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700432 * Populates the routing rules or makes hash group changes according to the
433 * route-path changes due to link failure, switch failure or link up. This
434 * method should only be called for one of these three possible event-types.
Saurav Dasdc7f2752018-03-18 21:28:15 -0700435 * Note that when a switch goes away, all of its links fail as well, but
436 * this is handled as a single switch removal event.
sanghofb7c7292015-04-13 15:15:58 -0700437 *
Saurav Dasdc7f2752018-03-18 21:28:15 -0700438 * @param linkDown the single failed link, or null for other conditions such
439 * as link-up or a removed switch
Saurav Das62ae6792017-05-15 15:34:25 -0700440 * @param linkUp the single link up, or null for other conditions such as
Saurav Dasdc7f2752018-03-18 21:28:15 -0700441 * link-down or a removed switch
442 * @param switchDown the removed switch, or null for other conditions such
443 * as link-down or link-up
444 * @param seenBefore true if this event is for a linkUp or linkDown for a
445 * seen link
446 */
447 // TODO This method should be refactored into three separated methods
Charles Chan9d2dd552018-06-19 20:56:33 -0700448 public void populateRoutingRulesForLinkStatusChange(Link linkDown, Link linkUp,
449 DeviceId switchDown, boolean seenBefore) {
Saurav Dasdc7f2752018-03-18 21:28:15 -0700450 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
451 .count() != 1) {
Saurav Das62ae6792017-05-15 15:34:25 -0700452 log.warn("Only one event can be handled for link status change .. aborting");
453 return;
454 }
Saurav Dasdc7f2752018-03-18 21:28:15 -0700455
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700456 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900457 statusLock.lock();
458 try {
sanghofb7c7292015-04-13 15:15:58 -0700459
460 if (populationStatus == Status.STARTED) {
Saurav Das261c3002017-06-13 15:35:54 -0700461 log.warn("Previous rule population is not finished. Cannot"
Saurav Das6430f412018-01-25 09:49:01 -0800462 + " proceeed with routingRules for Topology change");
Saurav Das62ae6792017-05-15 15:34:25 -0700463 return;
sanghofb7c7292015-04-13 15:15:58 -0700464 }
465
Saurav Das261c3002017-06-13 15:35:54 -0700466 // Take snapshots of the topology
sangho28d0b6d2015-05-07 13:30:57 -0700467 updatedEcmpSpgMap = new HashMap<>();
Saurav Das261c3002017-06-13 15:35:54 -0700468 Set<EdgePair> edgePairs = new HashSet<>();
sangho28d0b6d2015-05-07 13:30:57 -0700469 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH0637b162015-12-11 01:32:44 +0530470 EcmpShortestPathGraph ecmpSpgUpdated =
471 new EcmpShortestPathGraph(sw.id(), srManager);
sangho28d0b6d2015-05-07 13:30:57 -0700472 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700473 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
474 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700475 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700476 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
477 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
478 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700479 }
sangho28d0b6d2015-05-07 13:30:57 -0700480 }
481
Saurav Das6430f412018-01-25 09:49:01 -0800482 log.info("Starting to populate routing rules from Topology change");
sanghodf0153f2015-05-05 14:13:34 -0700483
sanghofb7c7292015-04-13 15:15:58 -0700484 Set<ArrayList<DeviceId>> routeChanges;
Saurav Das62ae6792017-05-15 15:34:25 -0700485 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700486 + "populationStatus is STARTED");
sanghofb7c7292015-04-13 15:15:58 -0700487 populationStatus = Status.STARTED;
Saurav Das6430f412018-01-25 09:49:01 -0800488 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
489 boolean hashGroupsChanged = false;
Saurav Das1b391d52016-11-29 14:27:25 -0800490 // try optimized re-routing
Saurav Das62ae6792017-05-15 15:34:25 -0700491 if (linkDown == null) {
492 // either a linkUp or a switchDown - compute all route changes by
493 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dascea556f2018-03-05 14:37:16 -0800494 routeChanges = computeRouteChange(switchDown);
Saurav Das62ae6792017-05-15 15:34:25 -0700495
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700496 // deal with linkUp of a seen-before link
Saurav Dasdc7f2752018-03-18 21:28:15 -0700497 if (linkUp != null && seenBefore) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700498 // link previously seen before
499 // do hash-bucket changes instead of a re-route
500 processHashGroupChange(routeChanges, false, null);
501 // clear out routesChanges so a re-route is not attempted
502 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800503 hashGroupsChanged = true;
Saurav Das62ae6792017-05-15 15:34:25 -0700504 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700505 // for a linkUp of a never-seen-before link
506 // let it fall through to a reroute of the routeChanges
Saurav Das62ae6792017-05-15 15:34:25 -0700507
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700508 //deal with switchDown
509 if (switchDown != null) {
510 processHashGroupChange(routeChanges, true, switchDown);
511 // clear out routesChanges so a re-route is not attempted
512 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800513 hashGroupsChanged = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700514 }
sanghofb7c7292015-04-13 15:15:58 -0700515 } else {
Saurav Das62ae6792017-05-15 15:34:25 -0700516 // link has gone down
517 // Compare existing ECMP SPG only with the link that went down
518 routeChanges = computeDamagedRoutes(linkDown);
Saurav Das68e1b6a2018-06-11 17:02:31 -0700519 processHashGroupChange(routeChanges, true, null);
520 // clear out routesChanges so a re-route is not attempted
521 routeChanges = ImmutableSet.of();
522 hashGroupsChanged = true;
Saurav Dasb149be12016-06-07 10:08:06 -0700523 }
524
sanghofb7c7292015-04-13 15:15:58 -0700525 if (routeChanges.isEmpty()) {
Saurav Das6430f412018-01-25 09:49:01 -0800526 if (hashGroupsChanged) {
527 log.info("Hash-groups changed for link status change");
528 } else {
529 log.info("No re-route or re-hash attempted for the link"
530 + " status change");
531 updatedEcmpSpgMap.keySet().forEach(devId -> {
532 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
533 log.debug("Updating ECMPspg for remaining dev:{}", devId);
534 });
535 }
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700536 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700537 populationStatus = Status.SUCCEEDED;
Saurav Das62ae6792017-05-15 15:34:25 -0700538 return;
sanghofb7c7292015-04-13 15:15:58 -0700539 }
540
Saurav Das62ae6792017-05-15 15:34:25 -0700541 // reroute of routeChanges
Saurav Das261c3002017-06-13 15:35:54 -0700542 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700543 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700544 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700545 log.info("Completed repopulation of rules for link-status change."
546 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700547 return;
sanghofb7c7292015-04-13 15:15:58 -0700548 } else {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700549 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sanghofb7c7292015-04-13 15:15:58 -0700550 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700551 log.warn("Failed to repopulate the rules for link status change.");
Saurav Das62ae6792017-05-15 15:34:25 -0700552 return;
sanghofb7c7292015-04-13 15:15:58 -0700553 }
pierdebd15c2019-04-19 20:55:53 +0200554 } catch (Exception e) {
555 log.error("populateRoutingRulesForLinkStatusChange thrown an exception: {}",
556 e.getMessage(), e);
557 populationStatus = Status.ABORTED;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900558 } finally {
559 statusLock.unlock();
sanghofb7c7292015-04-13 15:15:58 -0700560 }
561 }
562
Saurav Das62ae6792017-05-15 15:34:25 -0700563 /**
Saurav Das261c3002017-06-13 15:35:54 -0700564 * Processes a set a route-path changes by reprogramming routing rules and
565 * creating new hash-groups or editing them if necessary. This method also
566 * determines the next-hops for the route-path from the src-switch (target)
567 * of the path towards the dst-switch of the path.
Saurav Das62ae6792017-05-15 15:34:25 -0700568 *
Saurav Das261c3002017-06-13 15:35:54 -0700569 * @param routeChanges a set of route-path changes, where each route-path is
570 * a list with its first element the src-switch (target)
571 * of the path, and the second element the dst-switch of
572 * the path.
573 * @param edgePairs a set of edge-switches that are paired by configuration
574 * @param subnets a set of prefixes that need to be populated in the routing
575 * table of the target switch in the route-path. Can be null,
576 * in which case all the prefixes belonging to the dst-switch
577 * will be populated in the target switch
578 * @return true if successful in repopulating all routes
Saurav Das62ae6792017-05-15 15:34:25 -0700579 */
Saurav Das261c3002017-06-13 15:35:54 -0700580 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
581 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
582 // first make every entry two-elements
583 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
584 for (ArrayList<DeviceId> route : routeChanges) {
585 if (route.size() == 1) {
586 DeviceId dstSw = route.get(0);
587 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
588 if (ec == null) {
589 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
590 return false;
591 }
592 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
593 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
594 changedRoutes.add(Lists.newArrayList(target, dstSw));
595 });
596 });
597 } else {
598 DeviceId targetSw = route.get(0);
599 DeviceId dstSw = route.get(1);
600 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
601 }
602 }
603
604 // now process changedRoutes according to edgePairs
605 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
606 return false; //abort routing and fail fast
607 }
608
609 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Das6430f412018-01-25 09:49:01 -0800610 Set<DeviceId> updatedDevices = Sets.newHashSet();
611 if (!redoRoutingIndividualDests(subnets, changedRoutes,
612 updatedDevices)) {
Saurav Das261c3002017-06-13 15:35:54 -0700613 return false; //abort routing and fail fast
614 }
615
Saurav Das261c3002017-06-13 15:35:54 -0700616 // update ecmpSPG for all edge-pairs
617 for (EdgePair ep : edgePairs) {
618 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
619 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
620 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
621 }
Saurav Das6430f412018-01-25 09:49:01 -0800622
623 // here is where we update all devices not touched by this instance
624 updatedEcmpSpgMap.keySet().stream()
625 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
626 .filter(devId -> !updatedDevices.contains(devId))
627 .forEach(devId -> {
628 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
629 log.debug("Updating ECMPspg for remaining dev:{}", devId);
630 });
Saurav Das261c3002017-06-13 15:35:54 -0700631 return true;
632 }
633
634 /**
635 * Programs targetSw in the changedRoutes for given prefixes reachable by
636 * an edgePair. If no prefixes are given, the method will use configured
637 * subnets/prefixes. If some configured subnets belong only to a specific
638 * destination in the edgePair, then the target switch will be programmed
639 * only to that destination.
640 *
641 * @param edgePairs set of edge-pairs for which target will be programmed
642 * @param subnets a set of prefixes that need to be populated in the routing
643 * table of the target switch in the changedRoutes. Can be null,
644 * in which case all the configured prefixes belonging to the
645 * paired switches will be populated in the target switch
646 * @param changedRoutes a set of route-path changes, where each route-path is
647 * a list with its first element the src-switch (target)
648 * of the path, and the second element the dst-switch of
649 * the path.
650 * @return true if successful
651 */
652 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
653 Set<IpPrefix> subnets,
654 Set<ArrayList<DeviceId>> changedRoutes) {
655 for (EdgePair ep : edgePairs) {
656 // temp store for a target's changedRoutes to this edge-pair
657 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
658 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
659 while (i.hasNext()) {
660 ArrayList<DeviceId> route = i.next();
661 DeviceId dstSw = route.get(1);
662 if (ep.includes(dstSw)) {
663 // routeChange for edge pair found
664 // sort by target iff target is edge and remove from changedRoutes
665 DeviceId targetSw = route.get(0);
666 try {
667 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
668 continue;
669 }
670 } catch (DeviceConfigNotFoundException e) {
671 log.warn(e.getMessage() + "aborting redoRouting");
672 return false;
673 }
674 // route is from another edge to this edge-pair
675 if (targetRoutes.containsKey(targetSw)) {
676 targetRoutes.get(targetSw).add(route);
677 } else {
678 Set<ArrayList<DeviceId>> temp = new HashSet<>();
679 temp.add(route);
680 targetRoutes.put(targetSw, temp);
681 }
682 i.remove();
683 }
684 }
685 // so now for this edgepair we have a per target set of routechanges
686 // process target->edgePair route
687 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
688 targetRoutes.entrySet()) {
689 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
690 entry.getKey(), ep);
691 DeviceId targetSw = entry.getKey();
692 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
693 entry.getValue().forEach(route -> {
694 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
695 log.debug("route: target {} -> dst {} found with next-hops {}",
696 route.get(0), route.get(1), nhops);
697 perDstNextHops.put(route.get(1), nhops);
698 });
Charles Chan19b70032019-04-17 14:20:26 -0700699
700 List<Set<IpPrefix>> batchedSubnetDev1, batchedSubnetDev2;
701 if (subnets != null) {
702 batchedSubnetDev1 = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
703 batchedSubnetDev2 = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
704 } else {
705 batchedSubnetDev1 = config.getBatchedSubnets(ep.dev1);
706 batchedSubnetDev2 = config.getBatchedSubnets(ep.dev2);
707 }
708 List<Set<IpPrefix>> batchedSubnetBoth = Streams
709 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.intersection(a, b))
710 .filter(set -> !set.isEmpty())
711 .collect(Collectors.toList());
712 List<Set<IpPrefix>> batchedSubnetDev1Only = Streams
713 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.difference(a, b))
714 .filter(set -> !set.isEmpty())
715 .collect(Collectors.toList());
716 List<Set<IpPrefix>> batchedSubnetDev2Only = Streams
717 .zip(batchedSubnetDev1.stream(), batchedSubnetDev2.stream(), (a, b) -> Sets.difference(b, a))
718 .filter(set -> !set.isEmpty())
719 .collect(Collectors.toList());
720
Saurav Das6430f412018-01-25 09:49:01 -0800721 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
722 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Charles Chan19b70032019-04-17 14:20:26 -0700723
Saurav Das261c3002017-06-13 15:35:54 -0700724 // handle routing to subnets common to edge-pair
Saurav Das6430f412018-01-25 09:49:01 -0800725 // only if the targetSw is not part of the edge-pair and there
726 // exists a next hop to at least one of the devices in the edge-pair
727 if (!ep.includes(targetSw)
Charles Chan19b70032019-04-17 14:20:26 -0700728 && ((nhDev1 != null && !nhDev1.isEmpty()) || (nhDev2 != null && !nhDev2.isEmpty()))) {
729 log.trace("getSubnets on both {} and {}: {}", ep.dev1, ep.dev2, batchedSubnetBoth);
730 for (Set<IpPrefix> prefixes : batchedSubnetBoth) {
731 if (!populateEcmpRoutingRulePartial(
732 targetSw,
733 ep.dev1, ep.dev2,
734 perDstNextHops,
735 prefixes)) {
736 return false; // abort everything and fail fast
737 }
Saurav Das261c3002017-06-13 15:35:54 -0700738 }
Charles Chan19b70032019-04-17 14:20:26 -0700739
Saurav Das261c3002017-06-13 15:35:54 -0700740 }
Saurav Das6430f412018-01-25 09:49:01 -0800741 // handle routing to subnets that only belong to dev1 only if
742 // a next-hop exists from the target to dev1
Charles Chan19b70032019-04-17 14:20:26 -0700743 if (!batchedSubnetDev1Only.isEmpty() &&
744 batchedSubnetDev1Only.stream().anyMatch(subnet -> !subnet.isEmpty()) &&
745 nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700746 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800747 onlyDev1NextHops.put(ep.dev1, nhDev1);
Charles Chan19b70032019-04-17 14:20:26 -0700748 log.trace("getSubnets on {} only: {}", ep.dev1, batchedSubnetDev1Only);
749 for (Set<IpPrefix> prefixes : batchedSubnetDev1Only) {
750 if (!populateEcmpRoutingRulePartial(
751 targetSw,
752 ep.dev1, null,
753 onlyDev1NextHops,
754 prefixes)) {
755 return false; // abort everything and fail fast
756 }
Saurav Das261c3002017-06-13 15:35:54 -0700757 }
758 }
Saurav Das6430f412018-01-25 09:49:01 -0800759 // handle routing to subnets that only belong to dev2 only if
760 // a next-hop exists from the target to dev2
Charles Chan19b70032019-04-17 14:20:26 -0700761 if (!batchedSubnetDev2Only.isEmpty() &&
762 batchedSubnetDev2Only.stream().anyMatch(subnet -> !subnet.isEmpty()) &&
763 nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700764 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800765 onlyDev2NextHops.put(ep.dev2, nhDev2);
Charles Chan19b70032019-04-17 14:20:26 -0700766 log.trace("getSubnets on {} only: {}", ep.dev2, batchedSubnetDev2Only);
767 for (Set<IpPrefix> prefixes : batchedSubnetDev2Only) {
768 if (!populateEcmpRoutingRulePartial(
769 targetSw,
770 ep.dev2, null,
771 onlyDev2NextHops,
772 prefixes)) {
773 return false; // abort everything and fail fast
774 }
Saurav Das261c3002017-06-13 15:35:54 -0700775 }
776 }
777 }
778 // if it gets here it has succeeded for all targets to this edge-pair
779 }
780 return true;
781 }
782
783 /**
784 * Programs targetSw in the changedRoutes for given prefixes reachable by
785 * a destination switch that is not part of an edge-pair.
786 * If no prefixes are given, the method will use configured subnets/prefixes.
787 *
788 * @param subnets a set of prefixes that need to be populated in the routing
789 * table of the target switch in the changedRoutes. Can be null,
790 * in which case all the configured prefixes belonging to the
791 * paired switches will be populated in the target switch
792 * @param changedRoutes a set of route-path changes, where each route-path is
793 * a list with its first element the src-switch (target)
794 * of the path, and the second element the dst-switch of
795 * the path.
796 * @return true if successful
797 */
798 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Das6430f412018-01-25 09:49:01 -0800799 Set<ArrayList<DeviceId>> changedRoutes,
800 Set<DeviceId> updatedDevices) {
Saurav Das261c3002017-06-13 15:35:54 -0700801 // aggregate route-path changes for each dst device
802 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
803 new HashMap<>();
804 for (ArrayList<DeviceId> route: changedRoutes) {
805 DeviceId dstSw = route.get(1);
806 ArrayList<ArrayList<DeviceId>> deviceRoutes =
807 routesBydevice.get(dstSw);
808 if (deviceRoutes == null) {
809 deviceRoutes = new ArrayList<>();
810 routesBydevice.put(dstSw, deviceRoutes);
811 }
812 deviceRoutes.add(route);
813 }
814 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
815 ArrayList<ArrayList<DeviceId>> deviceRoutes =
816 routesBydevice.get(impactedDstDevice);
817 for (ArrayList<DeviceId> route: deviceRoutes) {
818 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
819 route.get(0), route.get(1));
820 DeviceId targetSw = route.get(0);
821 DeviceId dstSw = route.get(1); // same as impactedDstDevice
822 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Das8e46aa72018-01-09 17:38:44 -0800823 if (nextHops.isEmpty()) {
Saurav Das68e1b6a2018-06-11 17:02:31 -0700824 log.debug("Could not find next hop from target:{} --> dst {} "
Saurav Das8e46aa72018-01-09 17:38:44 -0800825 + "skipping this route", targetSw, dstSw);
826 continue;
827 }
Saurav Das261c3002017-06-13 15:35:54 -0700828 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
829 nhops.put(dstSw, nextHops);
830 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
831 (subnets == null) ? Sets.newHashSet() : subnets)) {
832 return false; // abort routing and fail fast
833 }
834 log.debug("Populating flow rules from target: {} to dst: {}"
835 + " is successful", targetSw, dstSw);
836 }
837 //Only if all the flows for all impacted routes to a
838 //specific target are pushed successfully, update the
839 //ECMP graph for that target. Or else the next event
840 //would not see any changes in the ECMP graphs.
841 //In another case, the target switch has gone away, so
842 //routes can't be installed. In that case, the current map
843 //is updated here, without any flows being pushed.
844 currentEcmpSpgMap.put(impactedDstDevice,
845 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Das6430f412018-01-25 09:49:01 -0800846 updatedDevices.add(impactedDstDevice);
Saurav Das261c3002017-06-13 15:35:54 -0700847 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
848 }
849 return true;
850 }
851
852 /**
853 * Populate ECMP rules for subnets from target to destination via nexthops.
854 *
855 * @param targetSw Device ID of target switch in which rules will be programmed
856 * @param destSw1 Device ID of final destination switch to which the rules will forward
857 * @param destSw2 Device ID of paired destination switch to which the rules will forward
858 * A null deviceId indicates packets should only be sent to destSw1
Saurav Das97241862018-02-14 14:14:54 -0800859 * @param nextHops Map of a set of next hops per destSw
Saurav Das261c3002017-06-13 15:35:54 -0700860 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
861 * @return true if it succeeds in populating rules
862 */ // refactor
863 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
864 DeviceId destSw1,
865 DeviceId destSw2,
866 Map<DeviceId, Set<DeviceId>> nextHops,
867 Set<IpPrefix> subnets) {
868 boolean result;
869 // If both target switch and dest switch are edge routers, then set IP
870 // rule for both subnet and router IP.
871 boolean targetIsEdge;
872 boolean dest1IsEdge;
873 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
874 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
875
876 try {
877 targetIsEdge = config.isEdgeDevice(targetSw);
878 dest1IsEdge = config.isEdgeDevice(destSw1);
879 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
880 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
881 if (destSw2 != null) {
882 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
883 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
884 }
885 } catch (DeviceConfigNotFoundException e) {
886 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Das62ae6792017-05-15 15:34:25 -0700887 return false;
888 }
Saurav Das261c3002017-06-13 15:35:54 -0700889
890 if (targetIsEdge && dest1IsEdge) {
Charles Chan19b70032019-04-17 14:20:26 -0700891 List<Set<IpPrefix>> batchedSubnets;
892 if (subnets != null && !subnets.isEmpty()) {
893 batchedSubnets = Lists.<Set<IpPrefix>>newArrayList(Sets.newHashSet(subnets));
894 } else {
895 batchedSubnets = config.getBatchedSubnets(destSw1);
896 }
Saurav Das97241862018-02-14 14:14:54 -0800897 // XXX - Rethink this - ignoring routerIPs in all other switches
898 // even edge to edge switches
Saurav Das261c3002017-06-13 15:35:54 -0700899 /*subnets.add(dest1RouterIpv4.toIpPrefix());
900 if (dest1RouterIpv6 != null) {
901 subnets.add(dest1RouterIpv6.toIpPrefix());
902 }
903 if (destSw2 != null && dest2RouterIpv4 != null) {
904 subnets.add(dest2RouterIpv4.toIpPrefix());
905 if (dest2RouterIpv6 != null) {
906 subnets.add(dest2RouterIpv6.toIpPrefix());
907 }
908 }*/
Charles Chan19b70032019-04-17 14:20:26 -0700909 log.trace("getSubnets on {}: {}", destSw1, batchedSubnets);
910 for (Set<IpPrefix> prefixes : batchedSubnets) {
911 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
912 + "for subnets {}", targetSw, destSw1,
913 (destSw2 != null) ? ("& " + destSw2) : "",
914 prefixes);
915 if (!rulePopulator.populateIpRuleForSubnet(targetSw, prefixes, destSw1, destSw2, nextHops)) {
916 return false;
917 }
Saurav Das261c3002017-06-13 15:35:54 -0700918 }
Saurav Das62ae6792017-05-15 15:34:25 -0700919 }
Saurav Das261c3002017-06-13 15:35:54 -0700920
921 if (!targetIsEdge && dest1IsEdge) {
922 // MPLS rules in all non-edge target devices. These rules are for
923 // individual destinations, even if the dsts are part of edge-pairs.
924 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
925 + "all MPLS rules", targetSw, destSw1);
926 result = rulePopulator.populateMplsRule(targetSw, destSw1,
927 nextHops.get(destSw1),
928 dest1RouterIpv4);
929 if (!result) {
930 return false;
931 }
932 if (dest1RouterIpv6 != null) {
Saurav Das97241862018-02-14 14:14:54 -0800933 int v4sid = 0, v6sid = 0;
934 try {
935 v4sid = config.getIPv4SegmentId(destSw1);
936 v6sid = config.getIPv6SegmentId(destSw1);
937 } catch (DeviceConfigNotFoundException e) {
938 log.warn(e.getMessage());
939 }
940 if (v4sid != v6sid) {
941 result = rulePopulator.populateMplsRule(targetSw, destSw1,
942 nextHops.get(destSw1),
943 dest1RouterIpv6);
944 if (!result) {
945 return false;
946 }
Saurav Das261c3002017-06-13 15:35:54 -0700947 }
948 }
949 }
950
Andreas Pantelopoulosfc4bc2a2018-03-12 16:30:20 -0700951 if (!targetIsEdge && !dest1IsEdge) {
952 // MPLS rules for inter-connected spines
953 // can be merged with above if, left it here for clarity
954 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
955 + "all MPLS rules", targetSw, destSw1);
956
957 result = rulePopulator.populateMplsRule(targetSw, destSw1,
958 nextHops.get(destSw1),
959 dest1RouterIpv4);
960 if (!result) {
961 return false;
962 }
963
964 if (dest1RouterIpv6 != null) {
965 int v4sid = 0, v6sid = 0;
966 try {
967 v4sid = config.getIPv4SegmentId(destSw1);
968 v6sid = config.getIPv6SegmentId(destSw1);
969 } catch (DeviceConfigNotFoundException e) {
970 log.warn(e.getMessage());
971 }
972 if (v4sid != v6sid) {
973 result = rulePopulator.populateMplsRule(targetSw, destSw1,
974 nextHops.get(destSw1),
975 dest1RouterIpv6);
976 if (!result) {
977 return false;
978 }
979 }
980 }
981 }
982
Saurav Das261c3002017-06-13 15:35:54 -0700983 // To save on ECMP groups
984 // avoid MPLS rules in non-edge-devices to non-edge-devices
985 // avoid MPLS transit rules in edge-devices
986 // avoid loopback IP rules in edge-devices to non-edge-devices
987 return true;
Saurav Das62ae6792017-05-15 15:34:25 -0700988 }
989
990 /**
991 * Processes a set a route-path changes by editing hash groups.
992 *
993 * @param routeChanges a set of route-path changes, where each route-path is
994 * a list with its first element the src-switch of the path
995 * and the second element the dst-switch of the path.
996 * @param linkOrSwitchFailed true if the route changes are for a failed
997 * switch or linkDown event
998 * @param failedSwitch the switchId if the route changes are for a failed switch,
999 * otherwise null
1000 */
1001 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
1002 boolean linkOrSwitchFailed,
1003 DeviceId failedSwitch) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001004 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
1005 // first, ensure each routeChanges entry has two elements
Saurav Das62ae6792017-05-15 15:34:25 -07001006 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001007 if (route.size() == 1) {
1008 // route-path changes are from everyone else to this switch
1009 DeviceId dstSw = route.get(0);
1010 srManager.deviceService.getAvailableDevices().forEach(sw -> {
1011 if (!sw.id().equals(dstSw)) {
1012 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
1013 }
1014 });
1015 } else {
1016 changedRoutes.add(route);
Saurav Das62ae6792017-05-15 15:34:25 -07001017 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001018 }
Saurav Das6430f412018-01-25 09:49:01 -08001019 boolean someFailed = false;
1020 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001021 for (ArrayList<DeviceId> route : changedRoutes) {
1022 DeviceId targetSw = route.get(0);
1023 DeviceId dstSw = route.get(1);
Saurav Das62ae6792017-05-15 15:34:25 -07001024 if (linkOrSwitchFailed) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001025 boolean success = fixHashGroupsForRoute(route, true);
Saurav Das62ae6792017-05-15 15:34:25 -07001026 // it's possible that we cannot fix hash groups for a route
1027 // if the target switch has failed. Nevertheless the ecmp graph
1028 // for the impacted switch must still be updated.
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001029 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001030 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1031 currentEcmpSpgMap.remove(targetSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001032 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Das62ae6792017-05-15 15:34:25 -07001033 + "target:{}", dstSw, targetSw);
Saurav Das6430f412018-01-25 09:49:01 -08001034 updatedDevices.add(targetSw);
1035 updatedDevices.add(dstSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001036 continue;
Saurav Das62ae6792017-05-15 15:34:25 -07001037 }
1038 //linkfailed - update both sides
Saurav Das62ae6792017-05-15 15:34:25 -07001039 if (success) {
1040 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001041 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Das6430f412018-01-25 09:49:01 -08001042 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1043 + " or switchdown", dstSw, targetSw);
1044 updatedDevices.add(targetSw);
1045 updatedDevices.add(dstSw);
1046 } else {
1047 someFailed = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001048 }
1049 } else {
1050 //linkup of seen before link
1051 boolean success = fixHashGroupsForRoute(route, false);
1052 if (success) {
1053 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1054 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1055 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Das62ae6792017-05-15 15:34:25 -07001056 targetSw, dstSw);
Saurav Das6430f412018-01-25 09:49:01 -08001057 updatedDevices.add(targetSw);
1058 updatedDevices.add(dstSw);
1059 } else {
1060 someFailed = true;
Saurav Das62ae6792017-05-15 15:34:25 -07001061 }
1062 }
1063 }
Saurav Das6430f412018-01-25 09:49:01 -08001064 if (!someFailed) {
1065 // here is where we update all devices not touched by this instance
1066 updatedEcmpSpgMap.keySet().stream()
1067 .filter(devId -> !updatedDevices.contains(devId))
1068 .forEach(devId -> {
1069 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1070 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1071 });
1072 }
Saurav Das62ae6792017-05-15 15:34:25 -07001073 }
1074
1075 /**
1076 * Edits hash groups in the src-switch (targetSw) of a route-path by
1077 * calling the groupHandler to either add or remove buckets in an existing
1078 * hash group.
1079 *
1080 * @param route a single list representing a route-path where the first element
1081 * is the src-switch (targetSw) of the route-path and the
1082 * second element is the dst-switch
1083 * @param revoke true if buckets in the hash-groups need to be removed;
1084 * false if buckets in the hash-groups need to be added
1085 * @return true if the hash group editing is successful
1086 */
1087 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1088 boolean revoke) {
1089 DeviceId targetSw = route.get(0);
1090 if (route.size() < 2) {
1091 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1092 return false;
1093 }
1094 DeviceId destSw = route.get(1);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001095 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001096 targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001097 // figure out the new next hops at the targetSw towards the destSw
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001098 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001099 // call group handler to change hash group at targetSw
1100 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1101 if (grpHandler == null) {
1102 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1103 + " {} hash group buckets for route:{} ", targetSw,
1104 (revoke) ? "revoke" : "repopulate", route);
1105 return false;
1106 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001107 log.debug("{} hash-groups buckets For Route {} -> {} to new next-hops {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001108 (revoke) ? "revoke" : "repopulating",
1109 targetSw, destSw, nextHops);
1110 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1111 destSw, true)
1112 : grpHandler.fixHashGroups(targetSw, nextHops,
1113 destSw, false);
1114 }
1115
1116 /**
Saurav Das261c3002017-06-13 15:35:54 -07001117 * Start the flow rule population process if it was never started. The
1118 * process finishes successfully when all flow rules are set and stops with
1119 * ABORTED status when any groups required for flows is not set yet.
Saurav Das62ae6792017-05-15 15:34:25 -07001120 */
Saurav Das261c3002017-06-13 15:35:54 -07001121 public void startPopulationProcess() {
1122 statusLock.lock();
1123 try {
1124 if (populationStatus == Status.IDLE
1125 || populationStatus == Status.SUCCEEDED
1126 || populationStatus == Status.ABORTED) {
1127 populateAllRoutingRules();
sangho28d0b6d2015-05-07 13:30:57 -07001128 } else {
Saurav Das261c3002017-06-13 15:35:54 -07001129 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1130 populationStatus);
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001131 }
Saurav Das261c3002017-06-13 15:35:54 -07001132 } finally {
1133 statusLock.unlock();
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001134 }
sanghofb7c7292015-04-13 15:15:58 -07001135 }
1136
Saurav Dasb149be12016-06-07 10:08:06 -07001137 /**
Saurav Das261c3002017-06-13 15:35:54 -07001138 * Revoke rules of given subnet in all edge switches.
1139 *
1140 * @param subnets subnet being removed
1141 * @return true if succeed
1142 */
1143 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1144 statusLock.lock();
1145 try {
Charles Chand66d6712018-03-29 16:03:41 -07001146 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1147 .map(Device::id)
1148 .filter(this::shouldProgram)
1149 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das261c3002017-06-13 15:35:54 -07001150 } finally {
1151 statusLock.unlock();
1152 }
1153 }
1154
1155 /**
Charles Chan910be6a2017-08-23 14:46:43 -07001156 * Populates IP rules for a route that has direct connection to the switch
1157 * if the current instance is the master of the switch.
1158 *
1159 * @param deviceId device ID of the device that next hop attaches to
1160 * @param prefix IP prefix of the route
1161 * @param hostMac MAC address of the next hop
1162 * @param hostVlanId Vlan ID of the nexthop
1163 * @param outPort port where the next hop attaches to
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001164 * @param directHost host is of type direct or indirect
Charles Chan910be6a2017-08-23 14:46:43 -07001165 */
1166 void populateRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001167 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chand66d6712018-03-29 16:03:41 -07001168 if (shouldProgram(deviceId)) {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001169 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan910be6a2017-08-23 14:46:43 -07001170 }
1171 }
1172
1173 /**
1174 * Removes IP rules for a route when the next hop is gone.
1175 * if the current instance is the master of the switch.
1176 *
1177 * @param deviceId device ID of the device that next hop attaches to
1178 * @param prefix IP prefix of the route
1179 * @param hostMac MAC address of the next hop
1180 * @param hostVlanId Vlan ID of the nexthop
1181 * @param outPort port that next hop attaches to
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001182 * @param directHost host is of type direct or indirect
Charles Chan910be6a2017-08-23 14:46:43 -07001183 */
1184 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001185 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chand66d6712018-03-29 16:03:41 -07001186 if (shouldProgram(deviceId)) {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001187 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan910be6a2017-08-23 14:46:43 -07001188 }
1189 }
1190
Charles Chand66d6712018-03-29 16:03:41 -07001191 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1192 if (shouldProgram(deviceId)) {
1193 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1194 }
1195 }
1196
1197 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1198 if (shouldProgram(deviceId)) {
1199 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1200 }
1201 }
1202
1203 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1204 VlanId vlanId, boolean popVlan, boolean install) {
1205 if (shouldProgram(deviceId)) {
1206 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1207 }
1208 }
1209
1210 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1211 VlanId vlanId, boolean popVlan, boolean install) {
1212 if (shouldProgram(deviceId)) {
1213 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1214 vlanId, popVlan, install);
1215 }
1216 }
1217
Charles Chan910be6a2017-08-23 14:46:43 -07001218 /**
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -07001219 * Populates IP rules for a route when the next hop is double-tagged.
1220 *
1221 * @param deviceId device ID that next hop attaches to
1222 * @param prefix IP prefix of the route
1223 * @param hostMac MAC address of the next hop
1224 * @param innerVlan Inner Vlan ID of the next hop
1225 * @param outerVlan Outer Vlan ID of the next hop
1226 * @param outerTpid Outer TPID of the next hop
1227 * @param outPort port that the next hop attaches to
1228 */
1229 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1230 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1231 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1232 VlanId dummyVlan = srManager.allocateDummyVlanId(
1233 new ConnectPoint(deviceId, outPort), prefix.address());
1234 if (!dummyVlan.equals(VlanId.NONE)) {
1235 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1236 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1237 srManager.routingRulePopulator.processDoubleTaggedFilter(
1238 deviceId, outPort, outerVlan, innerVlan, true);
1239 } else {
1240 log.error("Failed to allocate dummy VLAN ID for host {} at {}/{}",
1241 prefix.address(), deviceId, outPort);
1242 }
1243 }
1244 }
1245
1246 /**
1247 * Revokes IP rules for a route when the next hop is double-tagged.
1248 *
1249 * @param deviceId device ID that next hop attaches to
1250 * @param prefix IP prefix of the route
1251 * @param hostMac MAC address of the next hop
1252 * @param innerVlan Inner Vlan ID of the next hop
1253 * @param outerVlan Outer Vlan ID of the next hop
1254 * @param outerTpid Outer TPID of the next hop
1255 * @param outPort port that the next hop attaches to
1256 */
1257 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1258 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1259 // Revoke route either if this node have the mastership (when device is available) or
1260 // if this node is the leader (even when device is unavailable)
1261 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1262 if (srManager.deviceService.isAvailable(deviceId)) {
1263 // Master node will revoke specified rule.
1264 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1265 return;
1266 }
1267
1268 // isLocalMaster will return false when the device is unavailable.
1269 // Verify if this node is the leader in that case.
1270 NodeId leader = srManager.leadershipService.runForLeadership(
1271 deviceId.toString()).leaderNodeId();
1272 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1273 // Leader node will revoke specified rule.
1274 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1275 return;
1276 }
1277 }
1278
1279 VlanId dummyVlan = srManager.dummyVlanIdStore().get(new DummyVlanIdStoreKey(
1280 new ConnectPoint(deviceId, outPort), prefix.address()));
1281 if (dummyVlan == null) {
1282 log.error("Failed to get dummyVlanId for host {} at {}/{}.",
1283 prefix.address(), deviceId, outPort);
1284 } else {
1285 srManager.routingRulePopulator.revokeDoubleTaggedRoute(
1286 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1287 srManager.routingRulePopulator.processDoubleTaggedFilter(
1288 deviceId, outPort, outerVlan, innerVlan, false);
1289 }
1290 }
1291
1292
1293 /**
Saurav Das261c3002017-06-13 15:35:54 -07001294 * Remove ECMP graph entry for the given device. Typically called when
1295 * device is no longer available.
1296 *
1297 * @param deviceId the device for which graphs need to be purged
1298 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001299 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das6430f412018-01-25 09:49:01 -08001300 statusLock.lock();
1301 try {
Saurav Das6430f412018-01-25 09:49:01 -08001302 if (populationStatus == Status.STARTED) {
1303 log.warn("Previous rule population is not finished. Cannot"
1304 + " proceeed with purgeEcmpGraph for {}", deviceId);
1305 return;
1306 }
1307 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1308 currentEcmpSpgMap.remove(deviceId);
1309 if (updatedEcmpSpgMap != null) {
1310 updatedEcmpSpgMap.remove(deviceId);
1311 }
1312 } finally {
1313 statusLock.unlock();
Saurav Das261c3002017-06-13 15:35:54 -07001314 }
1315 }
1316
Saurav Das00e553b2018-04-21 17:19:48 -07001317 /**
1318 * Attempts a full reroute of route-paths if topology has changed relatively
1319 * close to a mastership change event. Does not do a reroute if mastership
1320 * change is due to reasons other than a ONOS cluster event - for example a
1321 * call to balance-masters, or a switch up/down event.
1322 *
1323 * @param devId the device identifier for which mastership has changed
1324 * @param me the mastership event
1325 */
1326 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1327 // give small delay to absorb mastership events that are caused by
1328 // device that has disconnected from cluster
Saurav Das49368392018-04-23 18:42:12 -07001329 executorServiceMstChg.schedule(new MasterChange(devId, me),
1330 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das00e553b2018-04-21 17:19:48 -07001331 }
1332
1333 protected final class MasterChange implements Runnable {
1334 private DeviceId devId;
1335 private MastershipEvent me;
1336 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1337 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
Saurav Dasec683dc2018-04-27 18:42:30 -07001338 private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
Saurav Das68e1b6a2018-06-11 17:02:31 -07001339 private static final long FULL_REROUTE_THRESHOLD = 10000; // ms
Saurav Das00e553b2018-04-21 17:19:48 -07001340
1341 MasterChange(DeviceId devId, MastershipEvent me) {
1342 this.devId = devId;
1343 this.me = me;
1344 }
1345
1346 @Override
1347 public void run() {
1348 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1349 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1350
1351 // ignore event for lost switch if cluster event hasn't happened -
1352 // device down event will handle it
1353 if ((me.roleInfo().master() == null
1354 || !srManager.deviceService.isAvailable(devId))
1355 && !clusterEvent) {
1356 log.debug("Full reroute not required for lost device: {}/{} "
1357 + "clusterEvent/timeSince: {}/{}",
1358 devId, me.roleInfo(), clusterEvent, lce);
1359 return;
1360 }
1361
1362 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1363 long lde = Instant.now().toEpochMilli() - update;
1364 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1365
1366 // ignore event for recently connected switch if cluster event hasn't
1367 // happened - link up events will handle it
1368 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1369 && !clusterEvent) {
1370 log.debug("Full reroute not required for recently available"
1371 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1372 + "clusterEvent/timeSince: {}/{}",
1373 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1374 return;
1375 }
1376
Saurav Dasec683dc2018-04-27 18:42:30 -07001377 long lepe = Instant.now().toEpochMilli()
1378 - srManager.lastEdgePortEvent.toEpochMilli();
1379 boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
1380
Saurav Das00e553b2018-04-21 17:19:48 -07001381 // if it gets here, then mastership change is likely due to onos
1382 // instance failure, or network partition in onos cluster
1383 // normally a mastership change like this does not require re-programming
1384 // but if topology changes happen at the same time then we may miss events
1385 if (!isRoutingStable() && clusterEvent) {
Saurav Dasec683dc2018-04-27 18:42:30 -07001386 log.warn("Mastership changed for dev: {}/{} while programming route-paths "
Saurav Das00e553b2018-04-21 17:19:48 -07001387 + "due to clusterEvent {} ms ago .. attempting full reroute",
1388 devId, me.roleInfo(), lce);
1389 if (srManager.mastershipService.isLocalMaster(devId)) {
1390 // old master could have died when populating filters
1391 populatePortAddressingRules(devId);
1392 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001393 // old master could have died when creating groups
Saurav Das00e553b2018-04-21 17:19:48 -07001394 // XXX right now we have no fine-grained way to only make changes
Saurav Das68e1b6a2018-06-11 17:02:31 -07001395 // for the route paths affected by this device. Thus we do a
1396 // full reroute after purging all hash groups. We also try to do
1397 // it only once, irrespective of the number of devices
1398 // that changed mastership when their master instance died.
1399 long lfrr = Instant.now().toEpochMilli() - lastFullReroute.toEpochMilli();
1400 boolean doFullReroute = lfrr > FULL_REROUTE_THRESHOLD;
1401 if (doFullReroute) {
1402 lastFullReroute = Instant.now();
1403 for (Device dev : srManager.deviceService.getDevices()) {
1404 if (shouldProgram(dev.id())) {
1405 srManager.purgeHashedNextObjectiveStore(dev.id());
1406 }
1407 }
1408 // give small delay to ensure entire store is purged
1409 executorServiceFRR.schedule(new FullRerouteAfterPurge(),
1410 PURGE_DELAY,
1411 TimeUnit.MILLISECONDS);
1412 } else {
1413 log.warn("Full reroute attempted {} ms ago .. skipping", lfrr);
1414 }
Saurav Dasec683dc2018-04-27 18:42:30 -07001415
1416 } else if (edgePortEvent && clusterEvent) {
1417 log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
1418 + "while edge-port event happened {} ms ago "
1419 + " .. reprogramming all edge-ports",
1420 devId, me.roleInfo(), lce, lepe);
1421 if (shouldProgram(devId)) {
1422 srManager.deviceService.getPorts(devId).stream()
1423 .filter(p -> srManager.interfaceService
1424 .isConfigured(new ConnectPoint(devId, p.number())))
1425 .forEach(p -> srManager.processPortUpdated(devId, p));
1426 }
1427
Saurav Das00e553b2018-04-21 17:19:48 -07001428 } else {
1429 log.debug("Stable route-paths .. full reroute not attempted for "
1430 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1431 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1432 deviceEvent, lde, clusterEvent, lce);
1433 }
1434 }
1435 }
1436
Saurav Das68e1b6a2018-06-11 17:02:31 -07001437 /**
1438 * Performs a full reroute of routing rules in all the switches. Assumes
1439 * caller has purged hash groups from the nextObjective store, otherwise
1440 * re-uses ones available in the store.
1441 */
1442 protected final class FullRerouteAfterPurge implements Runnable {
1443 @Override
1444 public void run() {
1445 populateAllRoutingRules();
1446 }
1447 }
1448
1449
Saurav Das261c3002017-06-13 15:35:54 -07001450 //////////////////////////////////////
1451 // Routing helper methods and classes
1452 //////////////////////////////////////
1453
1454 /**
Saurav Das68e1b6a2018-06-11 17:02:31 -07001455 * Computes set of affected routes due to failed link. Assumes previous ecmp
1456 * shortest-path graph exists for a switch in order to compute affected
1457 * routes. If such a graph does not exist, the method returns null.
Saurav Dasb149be12016-06-07 10:08:06 -07001458 *
1459 * @param linkFail the failed link
1460 * @return the set of affected routes which may be empty if no routes were
Saurav Das68e1b6a2018-06-11 17:02:31 -07001461 * affected
Saurav Dasb149be12016-06-07 10:08:06 -07001462 */
sanghofb7c7292015-04-13 15:15:58 -07001463 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sanghofb7c7292015-04-13 15:15:58 -07001464 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1465
1466 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001467 log.debug("Computing the impacted routes for device {} due to link fail",
1468 sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001469 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001470 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001471 continue;
1472 }
Charles Chand66d6712018-03-29 16:03:41 -07001473 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001474 // check for mastership change since last run
1475 if (!lastProgrammed.contains(sw.id())) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001476 log.warn("New responsibility for this node to program dev:{}"
Saurav Das00e553b2018-04-21 17:19:48 -07001477 + " ... nuking current ECMPspg", sw.id());
1478 currentEcmpSpgMap.remove(sw.id());
1479 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001480 lastProgrammed.add(sw.id());
1481
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001482 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1483 if (ecmpSpg == null) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001484 log.warn("No existing ECMP graph for switch {}. Assuming "
1485 + "all route-paths have changed towards it.", rootSw);
1486 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
1487 if (targetSw.equals(rootSw)) {
1488 continue;
1489 }
1490 routes.add(Lists.newArrayList(targetSw, rootSw));
1491 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1492 }
1493 continue;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001494 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001495
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001496 if (log.isDebugEnabled()) {
1497 log.debug("Root switch: {}", rootSw);
1498 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1499 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1500 }
1501 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1502 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1503 // figure out if the broken link affected any route-paths in this graph
1504 for (Integer itrIdx : switchVia.keySet()) {
1505 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1506 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1507 switchVia.get(itrIdx);
1508 for (DeviceId targetSw : swViaMap.keySet()) {
1509 log.trace("TargetSwitch {} --> RootSwitch {}",
1510 targetSw, rootSw);
Saurav Dasb149be12016-06-07 10:08:06 -07001511 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1512 log.trace(" Via:");
Pier Ventreadb4ae62016-11-23 09:57:42 -08001513 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb149be12016-06-07 10:08:06 -07001514 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001515 Set<ArrayList<DeviceId>> subLinks =
1516 computeLinks(targetSw, rootSw, swViaMap);
1517 for (ArrayList<DeviceId> alink: subLinks) {
1518 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1519 alink.get(1).equals(linkFail.dst().deviceId()))
1520 ||
1521 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1522 alink.get(1).equals(linkFail.src().deviceId()))) {
1523 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1524 ArrayList<DeviceId> aRoute = new ArrayList<>();
1525 aRoute.add(targetSw); // switch with rules to populate
1526 aRoute.add(rootSw); // towards this destination
1527 routes.add(aRoute);
1528 break;
1529 }
sanghofb7c7292015-04-13 15:15:58 -07001530 }
1531 }
1532 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001533
sanghofb7c7292015-04-13 15:15:58 -07001534 }
sangho28d0b6d2015-05-07 13:30:57 -07001535
sanghofb7c7292015-04-13 15:15:58 -07001536 }
sanghofb7c7292015-04-13 15:15:58 -07001537 return routes;
1538 }
1539
Saurav Das1b391d52016-11-29 14:27:25 -08001540 /**
1541 * Computes set of affected routes due to new links or failed switches.
1542 *
Saurav Dasdc7f2752018-03-18 21:28:15 -07001543 * @param failedSwitch deviceId of failed switch if any
Saurav Das1b391d52016-11-29 14:27:25 -08001544 * @return the set of affected routes which may be empty if no routes were
1545 * affected
1546 */
Saurav Dascea556f2018-03-05 14:37:16 -08001547 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das261c3002017-06-13 15:35:54 -07001548 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das1b391d52016-11-29 14:27:25 -08001549 ImmutableSet.builder();
sanghofb7c7292015-04-13 15:15:58 -07001550
1551 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das261c3002017-06-13 15:35:54 -07001552 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001553 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001554 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001555 continue;
1556 }
Charles Chand66d6712018-03-29 16:03:41 -07001557 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das261c3002017-06-13 15:35:54 -07001558 if (log.isTraceEnabled()) {
1559 log.trace("Device links for dev: {}", rootSw);
1560 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1561 log.trace("{} -> {} ", link.src().deviceId(),
1562 link.dst().deviceId());
1563 }
Saurav Dasb149be12016-06-07 10:08:06 -07001564 }
Saurav Das00e553b2018-04-21 17:19:48 -07001565 // check for mastership change since last run
1566 if (!lastProgrammed.contains(sw.id())) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001567 log.warn("New responsibility for this node to program dev:{}"
Saurav Das00e553b2018-04-21 17:19:48 -07001568 + " ... nuking current ECMPspg", sw.id());
1569 currentEcmpSpgMap.remove(sw.id());
1570 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001571 lastProgrammed.add(sw.id());
Saurav Das261c3002017-06-13 15:35:54 -07001572 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1573 if (currEcmpSpg == null) {
1574 log.debug("No existing ECMP graph for device {}.. adding self as "
1575 + "changed route", rootSw);
1576 changedRtBldr.add(Lists.newArrayList(rootSw));
1577 continue;
1578 }
1579 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Dasdebcf882018-04-06 20:16:01 -07001580 if (newEcmpSpg == null) {
1581 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1582 continue;
1583 }
Saurav Das261c3002017-06-13 15:35:54 -07001584 if (log.isDebugEnabled()) {
1585 log.debug("Root switch: {}", rootSw);
1586 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1587 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1588 }
1589 // first use the updated/new map to compare to current/existing map
1590 // as new links may have come up
1591 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1592 // then use the current/existing map to compare to updated/new map
1593 // as switch may have been removed
1594 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho28d0b6d2015-05-07 13:30:57 -07001595 }
Saurav Das1b391d52016-11-29 14:27:25 -08001596 }
sanghofb7c7292015-04-13 15:15:58 -07001597
Saurav Dascea556f2018-03-05 14:37:16 -08001598 // handle clearing state for a failed switch in case the switch does
1599 // not have a pair, or the pair is not available
1600 if (failedSwitch != null) {
Charles Chan6dbcd252018-04-02 11:46:38 -07001601 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1602 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dascea556f2018-03-05 14:37:16 -08001603 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1604 srManager.deviceService.getDevices().forEach(dev -> {
1605 if (!dev.id().equals(failedSwitch) &&
1606 srManager.mastershipService.isLocalMaster(dev.id())) {
1607 log.debug(" : {}", dev.id());
1608 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1609 }
1610 });
1611 }
1612 }
1613
Saurav Das261c3002017-06-13 15:35:54 -07001614 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das1b391d52016-11-29 14:27:25 -08001615 for (ArrayList<DeviceId> route: changedRoutes) {
1616 log.debug("Route changes Target -> Root");
1617 if (route.size() == 1) {
1618 log.debug(" : all -> {}", route.get(0));
1619 } else {
1620 log.debug(" : {} -> {}", route.get(0), route.get(1));
1621 }
1622 }
1623 return changedRoutes;
1624 }
1625
1626 /**
1627 * For the root switch, searches all the target nodes reachable in the base
1628 * graph, and compares paths to the ones in the comp graph.
1629 *
1630 * @param base the graph that is indexed for all reachable target nodes
1631 * from the root node
1632 * @param comp the graph that the base graph is compared to
1633 * @param rootSw both ecmp graphs are calculated for the root node
1634 * @return all the routes that have changed in the base graph
1635 */
1636 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1637 EcmpShortestPathGraph comp,
1638 DeviceId rootSw) {
1639 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1640 ImmutableSet.builder();
1641 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1642 base.getAllLearnedSwitchesAndVia();
1643 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1644 comp.getAllLearnedSwitchesAndVia();
1645 for (Integer itrIdx : baseMap.keySet()) {
1646 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1647 baseMap.get(itrIdx);
1648 for (DeviceId targetSw : baseViaMap.keySet()) {
1649 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1650 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1651 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001652 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das1b391d52016-11-29 14:27:25 -08001653 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das261c3002017-06-13 15:35:54 -07001654 route.add(targetSw); // switch with rules to populate
1655 route.add(rootSw); // towards this destination
Saurav Das1b391d52016-11-29 14:27:25 -08001656 changedRoutesBuilder.add(route);
sanghofb7c7292015-04-13 15:15:58 -07001657 }
1658 }
sangho28d0b6d2015-05-07 13:30:57 -07001659 }
Saurav Das1b391d52016-11-29 14:27:25 -08001660 return changedRoutesBuilder.build();
sanghofb7c7292015-04-13 15:15:58 -07001661 }
1662
Saurav Das261c3002017-06-13 15:35:54 -07001663 /**
1664 * Returns the ECMP paths traversed to reach the target switch.
1665 *
1666 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1667 * @param targetSw the switch to reach from the root switch
1668 * @return the nodes traversed on ECMP paths to the target switch
1669 */
sanghofb7c7292015-04-13 15:15:58 -07001670 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das1b391d52016-11-29 14:27:25 -08001671 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sanghofb7c7292015-04-13 15:15:58 -07001672 for (Integer itrIdx : switchVia.keySet()) {
1673 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1674 switchVia.get(itrIdx);
Saurav Das1b391d52016-11-29 14:27:25 -08001675 if (swViaMap.get(targetSw) == null) {
sanghofb7c7292015-04-13 15:15:58 -07001676 continue;
1677 } else {
Saurav Das1b391d52016-11-29 14:27:25 -08001678 return swViaMap.get(targetSw);
sanghofb7c7292015-04-13 15:15:58 -07001679 }
1680 }
1681
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001682 return null;
sanghofb7c7292015-04-13 15:15:58 -07001683 }
1684
Saurav Das261c3002017-06-13 15:35:54 -07001685 /**
1686 * Utility method to break down a path from src to dst device into a collection
1687 * of links.
1688 *
1689 * @param src src device of the path
1690 * @param dst dst device of the path
1691 * @param viaMap path taken from src to dst device
1692 * @return collection of links in the path
1693 */
sanghofb7c7292015-04-13 15:15:58 -07001694 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1695 DeviceId dst,
1696 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1697 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1698 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1699 DeviceId linkSrc = src;
1700 DeviceId linkDst = dst;
1701 for (DeviceId viaDevice: via) {
1702 ArrayList<DeviceId> link = new ArrayList<>();
1703 linkDst = viaDevice;
1704 link.add(linkSrc);
1705 link.add(linkDst);
1706 subLinks.add(link);
1707 linkSrc = viaDevice;
1708 }
1709 ArrayList<DeviceId> link = new ArrayList<>();
1710 link.add(linkSrc);
1711 link.add(dst);
1712 subLinks.add(link);
1713 }
1714
1715 return subLinks;
1716 }
1717
Charles Chanc22cef32016-04-29 14:38:22 -07001718 /**
Charles Chand66d6712018-03-29 16:03:41 -07001719 * Determines whether this controller instance should program the
Saurav Das261c3002017-06-13 15:35:54 -07001720 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chand66d6712018-03-29 16:03:41 -07001721 * <p>
1722 * Once an instance is elected, it will be the only instance responsible for programming
1723 * both devices in the pair until it goes down.
Charles Chanc22cef32016-04-29 14:38:22 -07001724 *
Saurav Das261c3002017-06-13 15:35:54 -07001725 * @param deviceId device identifier to consider for routing
Charles Chand66d6712018-03-29 16:03:41 -07001726 * @return true if current instance should handle the routing for given device
Charles Chanc22cef32016-04-29 14:38:22 -07001727 */
Charles Chand66d6712018-03-29 16:03:41 -07001728 boolean shouldProgram(DeviceId deviceId) {
Charles Chanfbcb8812018-04-18 18:41:05 -07001729 Boolean cached = shouldProgramCache.get(deviceId);
1730 if (cached != null) {
Saurav Das00e553b2018-04-21 17:19:48 -07001731 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chanfbcb8812018-04-18 18:41:05 -07001732 return cached;
1733 }
1734
Charles Chand66d6712018-03-29 16:03:41 -07001735 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sangho80f11cb2015-04-01 13:05:26 -07001736
Charles Chand66d6712018-03-29 16:03:41 -07001737 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1738 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1739 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
Saurav Das68e1b6a2018-06-11 17:02:31 -07001740 log.debug("Evaluate shouldProgram {}/pair={}. currentNodeId={}, master={}, pairMaster={}",
Charles Chand66d6712018-03-29 16:03:41 -07001741 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1742
1743 // No pair device configured. Only handle when current instance is the master of the device
1744 if (!pairDeviceId.isPresent()) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001745 log.debug("No pair device. currentNodeId={}, master={}", currentNodeId, masterNodeId);
Charles Chand66d6712018-03-29 16:03:41 -07001746 return currentNodeId.equals(masterNodeId);
sangho80f11cb2015-04-01 13:05:26 -07001747 }
Charles Chand66d6712018-03-29 16:03:41 -07001748
1749 // Should not handle if current instance is not the master of either switch
1750 if (!currentNodeId.equals(masterNodeId) &&
1751 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001752 log.debug("Current nodeId {} is neither the master of target device {} nor pair device {}",
Charles Chand66d6712018-03-29 16:03:41 -07001753 currentNodeId, deviceId, pairDeviceId);
1754 return false;
1755 }
1756
1757 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1758
1759 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1760 if (v == null) {
1761 // There is no value in the map. Elect a node
1762 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1763 } else {
1764 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1765 // Use the node in the map if it is still alive and is a master of any of the two switches
1766 return v;
1767 } else {
1768 // Previously elected node is no longer the master of either switch. Re-elect a node.
1769 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1770 }
1771 }
1772 }));
1773
1774 if (king != null) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001775 log.debug("{} is king, should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001776 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chand66d6712018-03-29 16:03:41 -07001777 return king.equals(currentNodeId);
1778 } else {
1779 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001780 shouldProgramCache.remove(deviceId);
Charles Chand66d6712018-03-29 16:03:41 -07001781 return false;
1782 }
1783 }
1784
1785 /**
1786 * Elects a node who should take responsibility of programming devices.
1787 * @param nodeIds list of candidate node ID
1788 *
1789 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1790 */
1791 private NodeId elect(List<NodeId> nodeIds) {
1792 // Remove all null elements. This could happen when some device has no master
1793 nodeIds.removeAll(Collections.singleton(null));
1794 nodeIds.sort(null);
1795 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1796 }
1797
Charles Chanfbcb8812018-04-18 18:41:05 -07001798 void invalidateShouldProgramCache(DeviceId deviceId) {
1799 shouldProgramCache.remove(deviceId);
1800 }
1801
Charles Chand66d6712018-03-29 16:03:41 -07001802 /**
1803 * Returns a set of device ID, containing given device and its pair device if exist.
1804 *
1805 * @param deviceId Device ID
1806 * @return a set of device ID, containing given device and its pair device if exist.
1807 */
1808 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1809 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1810 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1811 return ret;
sangho80f11cb2015-04-01 13:05:26 -07001812 }
1813
Charles Chanc22cef32016-04-29 14:38:22 -07001814 /**
Saurav Das261c3002017-06-13 15:35:54 -07001815 * Returns the set of deviceIds which are the next hops from the targetSw
1816 * to the dstSw according to the latest ECMP spg.
1817 *
1818 * @param targetSw the switch for which the next-hops are desired
1819 * @param dstSw the switch to which the next-hops lead to from the targetSw
1820 * @return set of next hop deviceIds, could be empty if no next hops are found
1821 */
1822 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1823 boolean targetIsEdge = false;
1824 try {
1825 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1826 } catch (DeviceConfigNotFoundException e) {
1827 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1828 + "continuing to getNextHops", targetSw);
1829 }
1830
1831 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1832 if (ecmpSpg == null) {
1833 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1834 return ImmutableSet.of();
1835 }
1836 HashMap<Integer,
1837 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1838 ecmpSpg.getAllLearnedSwitchesAndVia();
1839 for (Integer itrIdx : switchVia.keySet()) {
1840 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1841 switchVia.get(itrIdx);
1842 for (DeviceId target : swViaMap.keySet()) {
1843 if (!target.equals(targetSw)) {
1844 continue;
1845 }
Saurav Das49368392018-04-23 18:42:12 -07001846 // optimization for spines to not use leaves to get
1847 // to a spine or other leaves. Also leaves should not use other
1848 // leaves to get to the destination
1849 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Das97241862018-02-14 14:14:54 -08001850 boolean pathdevIsEdge = false;
1851 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Das49368392018-04-23 18:42:12 -07001852 log.debug("Evaluating next-hop in path: {}", via);
Saurav Das97241862018-02-14 14:14:54 -08001853 for (DeviceId pathdev : via) {
1854 try {
1855 pathdevIsEdge = srManager.deviceConfiguration
1856 .isEdgeDevice(pathdev);
1857 } catch (DeviceConfigNotFoundException e) {
1858 log.warn(e.getMessage());
1859 }
1860 if (pathdevIsEdge) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001861 log.debug("Avoiding {} hop path for targetSw:{}"
Saurav Das97241862018-02-14 14:14:54 -08001862 + " --> dstSw:{} which goes through an edge"
1863 + " device {} in path {}", itrIdx,
1864 targetSw, dstSw, pathdev, via);
1865 return ImmutableSet.of();
1866 }
1867 }
1868 }
Saurav Das261c3002017-06-13 15:35:54 -07001869 }
1870 Set<DeviceId> nextHops = new HashSet<>();
1871 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1872 if (via.isEmpty()) {
1873 // the dstSw is the next-hop from the targetSw
1874 nextHops.add(dstSw);
1875 } else {
1876 // first elem is next-hop in each ECMP path
1877 nextHops.add(via.get(0));
1878 }
1879 }
Saurav Das49368392018-04-23 18:42:12 -07001880 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
1881 dstSw, nextHops);
Saurav Das261c3002017-06-13 15:35:54 -07001882 return nextHops;
1883 }
1884 }
Saurav Das49368392018-04-23 18:42:12 -07001885 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das261c3002017-06-13 15:35:54 -07001886 return ImmutableSet.of(); //no next-hops found
1887 }
1888
Saurav Das261c3002017-06-13 15:35:54 -07001889 //////////////////////////////////////
1890 // Filtering rule creation
1891 //////////////////////////////////////
1892
1893 /**
Saurav Dasf9332192017-02-18 14:05:44 -08001894 * Populates filtering rules for port, and punting rules
1895 * for gateway IPs, loopback IPs and arp/ndp traffic.
1896 * Should only be called by the master instance for this device/port.
sangho80f11cb2015-04-01 13:05:26 -07001897 *
1898 * @param deviceId Switch ID to set the rules
1899 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001900 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das07c74602016-04-27 18:35:50 -07001901 // Although device is added, sometimes device store does not have the
1902 // ports for this device yet. It results in missing filtering rules in the
1903 // switch. We will attempt it a few times. If it still does not work,
1904 // user can manually repopulate using CLI command sr-reroute-network
Charles Chan18fa4252017-02-08 16:10:40 -08001905 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001906 if (firstRun == null) {
1907 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das07c74602016-04-27 18:35:50 -07001908 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001909 executorService.schedule(new RetryFilters(deviceId, firstRun),
1910 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sangho80f11cb2015-04-01 13:05:26 -07001911 }
1912
1913 /**
Saurav Dasd1872b02016-12-02 15:43:47 -08001914 * RetryFilters populates filtering objectives for a device and keeps retrying
1915 * till the number of ports filtered are constant for a predefined number
1916 * of attempts.
1917 */
1918 protected final class RetryFilters implements Runnable {
1919 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1920 DeviceId devId;
1921 int counter;
1922 PortFilterInfo prevRun;
1923
1924 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das07c74602016-04-27 18:35:50 -07001925 devId = deviceId;
Saurav Dasd1872b02016-12-02 15:43:47 -08001926 prevRun = previousRun;
1927 counter = 0;
Saurav Das07c74602016-04-27 18:35:50 -07001928 }
1929
1930 @Override
1931 public void run() {
Charles Chan077314e2017-06-22 14:27:17 -07001932 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chan18fa4252017-02-08 16:10:40 -08001933 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001934 boolean sameResult = prevRun.equals(thisRun);
1935 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1936 thisRun, sameResult);
Ray Milkey614352e2018-02-26 09:36:31 -08001937 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Dasf9332192017-02-18 14:05:44 -08001938 // exponentially increasing intervals for retries
1939 executorService.schedule(this,
1940 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1941 TimeUnit.MILLISECONDS);
Saurav Dasd1872b02016-12-02 15:43:47 -08001942 if (!sameResult) {
1943 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1944 }
Saurav Das07c74602016-04-27 18:35:50 -07001945 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001946 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das07c74602016-04-27 18:35:50 -07001947 }
Saurav Das07c74602016-04-27 18:35:50 -07001948 }
sangho80f11cb2015-04-01 13:05:26 -07001949}