blob: 149e837a69357e17bb92b46979e211826390c2d7 [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;
sangho9b169e32015-04-14 16:27:13 -070026import org.onlab.packet.Ip4Address;
Pier Ventreadb4ae62016-11-23 09:57:42 -080027import org.onlab.packet.Ip6Address;
sangho80f11cb2015-04-01 13:05:26 -070028import org.onlab.packet.IpPrefix;
Charles Chan910be6a2017-08-23 14:46:43 -070029import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
Saurav Das261c3002017-06-13 15:35:54 -070031import org.onosproject.cluster.NodeId;
Saurav Das00e553b2018-04-21 17:19:48 -070032import org.onosproject.mastership.MastershipEvent;
Charles Chanc22cef32016-04-29 14:38:22 -070033import org.onosproject.net.ConnectPoint;
sangho80f11cb2015-04-01 13:05:26 -070034import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
sanghofb7c7292015-04-13 15:15:58 -070036import org.onosproject.net.Link;
Charles Chan910be6a2017-08-23 14:46:43 -070037import org.onosproject.net.PortNumber;
Charles Chan319d1a22015-11-03 10:42:14 -080038import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
39import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Das62ae6792017-05-15 15:34:25 -070040import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -070041import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
Charles Chand66d6712018-03-29 16:03:41 -070042import org.onosproject.store.serializers.KryoNamespaces;
43import org.onosproject.store.service.Serializer;
sangho80f11cb2015-04-01 13:05:26 -070044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -070047import java.time.Instant;
sangho80f11cb2015-04-01 13:05:26 -070048import java.util.ArrayList;
Charles Chand66d6712018-03-29 16:03:41 -070049import java.util.Collections;
sangho80f11cb2015-04-01 13:05:26 -070050import java.util.HashMap;
51import java.util.HashSet;
Saurav Das261c3002017-06-13 15:35:54 -070052import java.util.Iterator;
Charles Chand66d6712018-03-29 16:03:41 -070053import java.util.List;
Saurav Das261c3002017-06-13 15:35:54 -070054import java.util.Map;
Saurav Dasd1872b02016-12-02 15:43:47 -080055import java.util.Objects;
Charles Chan6dbcd252018-04-02 11:46:38 -070056import java.util.Optional;
sangho80f11cb2015-04-01 13:05:26 -070057import java.util.Set;
Saurav Das07c74602016-04-27 18:35:50 -070058import java.util.concurrent.ScheduledExecutorService;
59import java.util.concurrent.TimeUnit;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090060import java.util.concurrent.locks.Lock;
61import java.util.concurrent.locks.ReentrantLock;
Saurav Dasdc7f2752018-03-18 21:28:15 -070062import java.util.stream.Stream;
63
Pier Ventreadb4ae62016-11-23 09:57:42 -080064import static com.google.common.base.Preconditions.checkNotNull;
65import static java.util.concurrent.Executors.newScheduledThreadPool;
66import static org.onlab.util.Tools.groupedThreads;
sangho80f11cb2015-04-01 13:05:26 -070067
Charles Chanb7f75ac2016-01-11 18:28:54 -080068/**
69 * Default routing handler that is responsible for route computing and
70 * routing rule population.
71 */
sangho80f11cb2015-04-01 13:05:26 -070072public class DefaultRoutingHandler {
Saurav Dasf9332192017-02-18 14:05:44 -080073 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey092e9e22018-02-01 13:49:47 -080074 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Dasf9332192017-02-18 14:05:44 -080075 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasfbe74572017-08-03 18:30:35 -070076 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das00e553b2018-04-21 17:19:48 -070077 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Saurav Das68e1b6a2018-06-11 17:02:31 -070078 private static final long PURGE_DELAY = 1000; // ms
Charles Chanc22cef32016-04-29 14:38:22 -070079 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sangho80f11cb2015-04-01 13:05:26 -070080
81 private SegmentRoutingManager srManager;
82 private RoutingRulePopulator rulePopulator;
Shashikanth VH0637b162015-12-11 01:32:44 +053083 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
84 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho9b169e32015-04-14 16:27:13 -070085 private DeviceConfiguration config;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090086 private final Lock statusLock = new ReentrantLock();
87 private volatile Status populationStatus;
Yuta HIGUCHIebee2f12016-07-21 16:54:33 -070088 private ScheduledExecutorService executorService
Saurav Dasd1872b02016-12-02 15:43:47 -080089 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070090 private ScheduledExecutorService executorServiceMstChg
91 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
Saurav Das68e1b6a2018-06-11 17:02:31 -070092 private ScheduledExecutorService executorServiceFRR
93 = newScheduledThreadPool(1, groupedThreads("fullRR", "fullRR-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070094
Saurav Das00e553b2018-04-21 17:19:48 -070095 private Instant lastRoutingChange = Instant.EPOCH;
Saurav Das68e1b6a2018-06-11 17:02:31 -070096 private Instant lastFullReroute = Instant.EPOCH;
sangho80f11cb2015-04-01 13:05:26 -070097
Saurav Das00e553b2018-04-21 17:19:48 -070098 // Distributed store to keep track of ONOS instance that should program the
99 // device pair. There should be only one instance (the king) that programs the same pair.
Charles Chand66d6712018-03-29 16:03:41 -0700100 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chanfbcb8812018-04-18 18:41:05 -0700101 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chand66d6712018-03-29 16:03:41 -0700102
Saurav Das00e553b2018-04-21 17:19:48 -0700103 // Local store to keep track of all devices that this instance was responsible
104 // for programming in the last run. Helps to determine if mastership changed
105 // during a run - only relevant for programming as a result of topo change.
106 Set<DeviceId> lastProgrammed;
107
sangho80f11cb2015-04-01 13:05:26 -0700108 /**
109 * Represents the default routing population status.
110 */
111 public enum Status {
112 // population process is not started yet.
113 IDLE,
114
115 // population process started.
116 STARTED,
117
Srikanth Vavilapalli64505482015-04-21 13:04:13 -0700118 // population process was aborted due to errors, mostly for groups not
119 // found.
sangho80f11cb2015-04-01 13:05:26 -0700120 ABORTED,
121
122 // population process was finished successfully.
123 SUCCEEDED
124 }
125
126 /**
127 * Creates a DefaultRoutingHandler object.
128 *
129 * @param srManager SegmentRoutingManager object
130 */
Charles Chand66d6712018-03-29 16:03:41 -0700131 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700132 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
133 .withName("sr-should-program")
134 .withSerializer(Serializer.using(KryoNamespaces.API))
135 .withRelaxedReadConsistency()
136 .build().asJavaMap();
137 this.shouldProgramCache = Maps.newConcurrentMap();
138 update(srManager);
139 }
140
141 /**
142 * Updates a DefaultRoutingHandler object.
143 *
144 * @param srManager SegmentRoutingManager object
145 */
146 void update(SegmentRoutingManager srManager) {
sangho80f11cb2015-04-01 13:05:26 -0700147 this.srManager = srManager;
148 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho9b169e32015-04-14 16:27:13 -0700149 this.config = checkNotNull(srManager.deviceConfiguration);
sangho80f11cb2015-04-01 13:05:26 -0700150 this.populationStatus = Status.IDLE;
sanghofb7c7292015-04-13 15:15:58 -0700151 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das00e553b2018-04-21 17:19:48 -0700152 this.lastProgrammed = Sets.newConcurrentHashSet();
sangho80f11cb2015-04-01 13:05:26 -0700153 }
154
155 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700156 * Returns an immutable copy of the current ECMP shortest-path graph as
157 * computed by this controller instance.
158 *
Saurav Das261c3002017-06-13 15:35:54 -0700159 * @return immutable copy of the current ECMP graph
Saurav Das62ae6792017-05-15 15:34:25 -0700160 */
161 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
162 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
163 currentEcmpSpgMap.entrySet().forEach(entry -> {
164 if (entry.getValue() != null) {
165 builder.put(entry.getKey(), entry.getValue());
166 }
167 });
168 return builder.build();
169 }
170
Saurav Dasfbe74572017-08-03 18:30:35 -0700171 /**
172 * Acquires the lock used when making routing changes.
173 */
174 public void acquireRoutingLock() {
175 statusLock.lock();
176 }
177
178 /**
179 * Releases the lock used when making routing changes.
180 */
181 public void releaseRoutingLock() {
182 statusLock.unlock();
183 }
184
185 /**
186 * Determines if routing in the network has been stable in the last
187 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
188 * routing change timestamp.
189 *
190 * @return true if stable
191 */
192 public boolean isRoutingStable() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700193 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
194 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700195 log.trace("Routing stable since {}s", now - last);
Saurav Dasfbe74572017-08-03 18:30:35 -0700196 return (now - last) > STABLITY_THRESHOLD;
197 }
198
Saurav Das49368392018-04-23 18:42:12 -0700199 /**
200 * Gracefully shuts down the defaultRoutingHandler. Typically called when
201 * the app is deactivated
202 */
203 public void shutdown() {
204 executorService.shutdown();
205 executorServiceMstChg.shutdown();
Saurav Das68e1b6a2018-06-11 17:02:31 -0700206 executorServiceFRR.shutdown();
Saurav Das49368392018-04-23 18:42:12 -0700207 }
Saurav Dasfbe74572017-08-03 18:30:35 -0700208
Saurav Das261c3002017-06-13 15:35:54 -0700209 //////////////////////////////////////
210 // Route path handling
211 //////////////////////////////////////
212
Saurav Dase6c448a2018-01-18 12:07:33 -0800213 /* The following three methods represent the three major ways in which
214 * route-path handling is triggered in the network
Saurav Das261c3002017-06-13 15:35:54 -0700215 * a) due to configuration change
216 * b) due to route-added event
217 * c) due to change in the topology
218 */
219
Saurav Das62ae6792017-05-15 15:34:25 -0700220 /**
Saurav Das261c3002017-06-13 15:35:54 -0700221 * Populates all routing rules to all switches. Typically triggered at
222 * startup or after a configuration event.
sangho80f11cb2015-04-01 13:05:26 -0700223 */
Saurav Das62ae6792017-05-15 15:34:25 -0700224 public void populateAllRoutingRules() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700225 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900226 statusLock.lock();
227 try {
Saurav Das261c3002017-06-13 15:35:54 -0700228 if (populationStatus == Status.STARTED) {
229 log.warn("Previous rule population is not finished. Cannot"
230 + " proceed with populateAllRoutingRules");
231 return;
232 }
233
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900234 populationStatus = Status.STARTED;
235 rulePopulator.resetCounter();
Saurav Das261c3002017-06-13 15:35:54 -0700236 log.info("Starting to populate all routing rules");
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900237 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sangho80f11cb2015-04-01 13:05:26 -0700238
Saurav Das261c3002017-06-13 15:35:54 -0700239 // take a snapshot of the topology
240 updatedEcmpSpgMap = new HashMap<>();
241 Set<EdgePair> edgePairs = new HashSet<>();
242 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart61e24e12017-11-30 18:23:42 -0800243 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das261c3002017-06-13 15:35:54 -0700244 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart61e24e12017-11-30 18:23:42 -0800245 new EcmpShortestPathGraph(dstSw, srManager);
246 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700247 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
248 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700249 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700250 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
251 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
252 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700253 }
Charles Chand66d6712018-03-29 16:03:41 -0700254
255 if (!shouldProgram(dstSw)) {
Saurav Das00e553b2018-04-21 17:19:48 -0700256 lastProgrammed.remove(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900257 continue;
Saurav Das00e553b2018-04-21 17:19:48 -0700258 } else {
259 lastProgrammed.add(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900260 }
Saurav Das00e553b2018-04-21 17:19:48 -0700261 // To do a full reroute, assume all route-paths have changed
Charles Chand66d6712018-03-29 16:03:41 -0700262 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart61e24e12017-11-30 18:23:42 -0800263 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
264 if (targetSw.equals(dev)) {
Saurav Das261c3002017-06-13 15:35:54 -0700265 continue;
266 }
Jonathan Hart61e24e12017-11-30 18:23:42 -0800267 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das261c3002017-06-13 15:35:54 -0700268 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900269 }
Saurav Das261c3002017-06-13 15:35:54 -0700270 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900271
Saurav Das261c3002017-06-13 15:35:54 -0700272 if (!redoRouting(routeChanges, edgePairs, null)) {
273 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
274 populationStatus = Status.ABORTED;
275 log.warn("Failed to repopulate all routing rules.");
276 return;
sangho80f11cb2015-04-01 13:05:26 -0700277 }
278
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900279 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
280 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700281 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900282 rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700283 return;
pierdebd15c2019-04-19 20:55:53 +0200284 } catch (Exception e) {
285 log.error("populateAllRoutingRules thrown an exception: {}",
286 e.getMessage(), e);
287 populationStatus = Status.ABORTED;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900288 } finally {
289 statusLock.unlock();
sangho80f11cb2015-04-01 13:05:26 -0700290 }
sangho80f11cb2015-04-01 13:05:26 -0700291 }
292
sanghofb7c7292015-04-13 15:15:58 -0700293 /**
Saurav Das261c3002017-06-13 15:35:54 -0700294 * Populate rules from all other edge devices to the connect-point(s)
295 * specified for the given subnets.
296 *
297 * @param cpts connect point(s) of the subnets being added
298 * @param subnets subnets being added
Charles Chan910be6a2017-08-23 14:46:43 -0700299 */
300 // XXX refactor
Saurav Das261c3002017-06-13 15:35:54 -0700301 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan6db55b92017-09-11 15:21:57 -0700302 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
303 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
304 return;
305 }
306
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700307 lastRoutingChange = Instant.now();
Saurav Das261c3002017-06-13 15:35:54 -0700308 statusLock.lock();
309 try {
310 if (populationStatus == Status.STARTED) {
311 log.warn("Previous rule population is not finished. Cannot"
312 + " proceed with routing rules for added routes");
313 return;
314 }
315 populationStatus = Status.STARTED;
316 rulePopulator.resetCounter();
Charles Chan910be6a2017-08-23 14:46:43 -0700317 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
318 subnets, cpts);
Saurav Das6430f412018-01-25 09:49:01 -0800319 // In principle an update to a subnet/prefix should not require a
320 // new ECMPspg calculation as it is not a topology event. As a
321 // result, we use the current/existing ECMPspg in the updated map
322 // used by the redoRouting method.
Saurav Das6de6ffd2018-02-09 09:15:03 -0800323 if (updatedEcmpSpgMap == null) {
324 updatedEcmpSpgMap = new HashMap<>();
325 }
Saurav Das6430f412018-01-25 09:49:01 -0800326 currentEcmpSpgMap.entrySet().forEach(entry -> {
327 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase321cff2018-02-09 17:26:45 -0800328 if (log.isTraceEnabled()) {
329 log.trace("Root switch: {}", entry.getKey());
330 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Das6430f412018-01-25 09:49:01 -0800331 }
332 });
Saurav Das261c3002017-06-13 15:35:54 -0700333 Set<EdgePair> edgePairs = new HashSet<>();
334 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
335 boolean handleRouting = false;
336
337 if (cpts.size() == 2) {
338 // ensure connect points are edge-pairs
339 Iterator<ConnectPoint> iter = cpts.iterator();
340 DeviceId dev1 = iter.next().deviceId();
Charles Chan6dbcd252018-04-02 11:46:38 -0700341 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
342 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
343 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700344 } else {
345 log.warn("Connectpoints {} for subnets {} not on "
346 + "pair-devices.. aborting populateSubnet", cpts, subnets);
347 populationStatus = Status.ABORTED;
348 return;
349 }
350 for (ConnectPoint cp : cpts) {
Saurav Das6430f412018-01-25 09:49:01 -0800351 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
352 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700353 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800354 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
355 log.warn("populateSubnet: no updated graph for dev:{}"
356 + " ... creating", cp.deviceId());
357 }
Charles Chand66d6712018-03-29 16:03:41 -0700358 if (!shouldProgram(cp.deviceId())) {
Saurav Das261c3002017-06-13 15:35:54 -0700359 continue;
360 }
361 handleRouting = true;
362 }
363 } else {
364 // single connect point
365 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Das6430f412018-01-25 09:49:01 -0800366 if (updatedEcmpSpgMap.get(dstSw) == null) {
367 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700368 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800369 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
370 log.warn("populateSubnet: no updated graph for dev:{}"
371 + " ... creating", dstSw);
372 }
Charles Chand66d6712018-03-29 16:03:41 -0700373 handleRouting = shouldProgram(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700374 }
375
376 if (!handleRouting) {
377 log.debug("This instance is not handling ecmp routing to the "
378 + "connectPoint(s) {}", cpts);
379 populationStatus = Status.ABORTED;
380 return;
381 }
382
383 // if it gets here, this instance should handle routing for the
384 // connectpoint(s). Assume all route-paths have to be updated to
385 // the connectpoint(s) with the following exceptions
386 // 1. if target is non-edge no need for routing rules
387 // 2. if target is one of the connectpoints
388 for (ConnectPoint cp : cpts) {
389 DeviceId dstSw = cp.deviceId();
390 for (Device targetSw : srManager.deviceService.getDevices()) {
391 boolean isEdge = false;
392 try {
393 isEdge = config.isEdgeDevice(targetSw.id());
394 } catch (DeviceConfigNotFoundException e) {
Charles Chaneaf3c9b2018-02-16 17:20:54 -0800395 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
396 continue;
Saurav Das261c3002017-06-13 15:35:54 -0700397 }
Charles Chan6dbcd252018-04-02 11:46:38 -0700398 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700399 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chan6dbcd252018-04-02 11:46:38 -0700400 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das261c3002017-06-13 15:35:54 -0700401 continue;
402 }
403 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
404 }
405 }
406
407 if (!redoRouting(routeChanges, edgePairs, subnets)) {
408 log.debug("populateSubnet: populationStatus is ABORTED");
409 populationStatus = Status.ABORTED;
410 log.warn("Failed to repopulate the rules for subnet.");
411 return;
412 }
413
414 log.debug("populateSubnet: populationStatus is SUCCEEDED");
415 populationStatus = Status.SUCCEEDED;
416 log.info("Completed subnet population. Total # of rules pushed : {}",
417 rulePopulator.getCounter());
418 return;
419
pierdebd15c2019-04-19 20:55:53 +0200420 } catch (Exception e) {
421 log.error("populateSubnet thrown an exception: {}",
422 e.getMessage(), e);
423 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700424 } finally {
425 statusLock.unlock();
426 }
427 }
428
429 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700430 * Populates the routing rules or makes hash group changes according to the
431 * route-path changes due to link failure, switch failure or link up. This
432 * method should only be called for one of these three possible event-types.
Saurav Dasdc7f2752018-03-18 21:28:15 -0700433 * Note that when a switch goes away, all of its links fail as well, but
434 * this is handled as a single switch removal event.
sanghofb7c7292015-04-13 15:15:58 -0700435 *
Saurav Dasdc7f2752018-03-18 21:28:15 -0700436 * @param linkDown the single failed link, or null for other conditions such
437 * as link-up or a removed switch
Saurav Das62ae6792017-05-15 15:34:25 -0700438 * @param linkUp the single link up, or null for other conditions such as
Saurav Dasdc7f2752018-03-18 21:28:15 -0700439 * link-down or a removed switch
440 * @param switchDown the removed switch, or null for other conditions such
441 * as link-down or link-up
442 * @param seenBefore true if this event is for a linkUp or linkDown for a
443 * seen link
444 */
445 // TODO This method should be refactored into three separated methods
Charles Chan9d2dd552018-06-19 20:56:33 -0700446 public void populateRoutingRulesForLinkStatusChange(Link linkDown, Link linkUp,
447 DeviceId switchDown, boolean seenBefore) {
Saurav Dasdc7f2752018-03-18 21:28:15 -0700448 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
449 .count() != 1) {
Saurav Das62ae6792017-05-15 15:34:25 -0700450 log.warn("Only one event can be handled for link status change .. aborting");
451 return;
452 }
Saurav Dasdc7f2752018-03-18 21:28:15 -0700453
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700454 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900455 statusLock.lock();
456 try {
sanghofb7c7292015-04-13 15:15:58 -0700457
458 if (populationStatus == Status.STARTED) {
Saurav Das261c3002017-06-13 15:35:54 -0700459 log.warn("Previous rule population is not finished. Cannot"
Saurav Das6430f412018-01-25 09:49:01 -0800460 + " proceeed with routingRules for Topology change");
Saurav Das62ae6792017-05-15 15:34:25 -0700461 return;
sanghofb7c7292015-04-13 15:15:58 -0700462 }
463
Saurav Das261c3002017-06-13 15:35:54 -0700464 // Take snapshots of the topology
sangho28d0b6d2015-05-07 13:30:57 -0700465 updatedEcmpSpgMap = new HashMap<>();
Saurav Das261c3002017-06-13 15:35:54 -0700466 Set<EdgePair> edgePairs = new HashSet<>();
sangho28d0b6d2015-05-07 13:30:57 -0700467 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH0637b162015-12-11 01:32:44 +0530468 EcmpShortestPathGraph ecmpSpgUpdated =
469 new EcmpShortestPathGraph(sw.id(), srManager);
sangho28d0b6d2015-05-07 13:30:57 -0700470 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700471 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
472 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700473 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700474 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
475 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
476 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700477 }
sangho28d0b6d2015-05-07 13:30:57 -0700478 }
479
Saurav Das6430f412018-01-25 09:49:01 -0800480 log.info("Starting to populate routing rules from Topology change");
sanghodf0153f2015-05-05 14:13:34 -0700481
sanghofb7c7292015-04-13 15:15:58 -0700482 Set<ArrayList<DeviceId>> routeChanges;
Saurav Das62ae6792017-05-15 15:34:25 -0700483 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700484 + "populationStatus is STARTED");
sanghofb7c7292015-04-13 15:15:58 -0700485 populationStatus = Status.STARTED;
Saurav Das6430f412018-01-25 09:49:01 -0800486 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
487 boolean hashGroupsChanged = false;
Saurav Das1b391d52016-11-29 14:27:25 -0800488 // try optimized re-routing
Saurav Das62ae6792017-05-15 15:34:25 -0700489 if (linkDown == null) {
490 // either a linkUp or a switchDown - compute all route changes by
491 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dascea556f2018-03-05 14:37:16 -0800492 routeChanges = computeRouteChange(switchDown);
Saurav Das62ae6792017-05-15 15:34:25 -0700493
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700494 // deal with linkUp of a seen-before link
Saurav Dasdc7f2752018-03-18 21:28:15 -0700495 if (linkUp != null && seenBefore) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700496 // link previously seen before
497 // do hash-bucket changes instead of a re-route
498 processHashGroupChange(routeChanges, false, null);
499 // clear out routesChanges so a re-route is not attempted
500 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800501 hashGroupsChanged = true;
Saurav Das62ae6792017-05-15 15:34:25 -0700502 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700503 // for a linkUp of a never-seen-before link
504 // let it fall through to a reroute of the routeChanges
Saurav Das62ae6792017-05-15 15:34:25 -0700505
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700506 //deal with switchDown
507 if (switchDown != null) {
508 processHashGroupChange(routeChanges, true, switchDown);
509 // clear out routesChanges so a re-route is not attempted
510 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800511 hashGroupsChanged = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700512 }
sanghofb7c7292015-04-13 15:15:58 -0700513 } else {
Saurav Das62ae6792017-05-15 15:34:25 -0700514 // link has gone down
515 // Compare existing ECMP SPG only with the link that went down
516 routeChanges = computeDamagedRoutes(linkDown);
Saurav Das68e1b6a2018-06-11 17:02:31 -0700517 processHashGroupChange(routeChanges, true, null);
518 // clear out routesChanges so a re-route is not attempted
519 routeChanges = ImmutableSet.of();
520 hashGroupsChanged = true;
Saurav Dasb149be12016-06-07 10:08:06 -0700521 }
522
sanghofb7c7292015-04-13 15:15:58 -0700523 if (routeChanges.isEmpty()) {
Saurav Das6430f412018-01-25 09:49:01 -0800524 if (hashGroupsChanged) {
525 log.info("Hash-groups changed for link status change");
526 } else {
527 log.info("No re-route or re-hash attempted for the link"
528 + " status change");
529 updatedEcmpSpgMap.keySet().forEach(devId -> {
530 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
531 log.debug("Updating ECMPspg for remaining dev:{}", devId);
532 });
533 }
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700534 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700535 populationStatus = Status.SUCCEEDED;
Saurav Das62ae6792017-05-15 15:34:25 -0700536 return;
sanghofb7c7292015-04-13 15:15:58 -0700537 }
538
Saurav Das62ae6792017-05-15 15:34:25 -0700539 // reroute of routeChanges
Saurav Das261c3002017-06-13 15:35:54 -0700540 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700541 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700542 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700543 log.info("Completed repopulation of rules for link-status change."
544 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700545 return;
sanghofb7c7292015-04-13 15:15:58 -0700546 } else {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700547 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sanghofb7c7292015-04-13 15:15:58 -0700548 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700549 log.warn("Failed to repopulate the rules for link status change.");
Saurav Das62ae6792017-05-15 15:34:25 -0700550 return;
sanghofb7c7292015-04-13 15:15:58 -0700551 }
pierdebd15c2019-04-19 20:55:53 +0200552 } catch (Exception e) {
553 log.error("populateRoutingRulesForLinkStatusChange thrown an exception: {}",
554 e.getMessage(), e);
555 populationStatus = Status.ABORTED;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900556 } finally {
557 statusLock.unlock();
sanghofb7c7292015-04-13 15:15:58 -0700558 }
559 }
560
Saurav Das62ae6792017-05-15 15:34:25 -0700561 /**
Saurav Das261c3002017-06-13 15:35:54 -0700562 * Processes a set a route-path changes by reprogramming routing rules and
563 * creating new hash-groups or editing them if necessary. This method also
564 * determines the next-hops for the route-path from the src-switch (target)
565 * of the path towards the dst-switch of the path.
Saurav Das62ae6792017-05-15 15:34:25 -0700566 *
Saurav Das261c3002017-06-13 15:35:54 -0700567 * @param routeChanges a set of route-path changes, where each route-path is
568 * a list with its first element the src-switch (target)
569 * of the path, and the second element the dst-switch of
570 * the path.
571 * @param edgePairs a set of edge-switches that are paired by configuration
572 * @param subnets a set of prefixes that need to be populated in the routing
573 * table of the target switch in the route-path. Can be null,
574 * in which case all the prefixes belonging to the dst-switch
575 * will be populated in the target switch
576 * @return true if successful in repopulating all routes
Saurav Das62ae6792017-05-15 15:34:25 -0700577 */
Saurav Das261c3002017-06-13 15:35:54 -0700578 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
579 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
580 // first make every entry two-elements
581 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
582 for (ArrayList<DeviceId> route : routeChanges) {
583 if (route.size() == 1) {
584 DeviceId dstSw = route.get(0);
585 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
586 if (ec == null) {
587 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
588 return false;
589 }
590 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
591 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
592 changedRoutes.add(Lists.newArrayList(target, dstSw));
593 });
594 });
595 } else {
596 DeviceId targetSw = route.get(0);
597 DeviceId dstSw = route.get(1);
598 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
599 }
600 }
601
602 // now process changedRoutes according to edgePairs
603 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
604 return false; //abort routing and fail fast
605 }
606
607 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Das6430f412018-01-25 09:49:01 -0800608 Set<DeviceId> updatedDevices = Sets.newHashSet();
609 if (!redoRoutingIndividualDests(subnets, changedRoutes,
610 updatedDevices)) {
Saurav Das261c3002017-06-13 15:35:54 -0700611 return false; //abort routing and fail fast
612 }
613
Saurav Das261c3002017-06-13 15:35:54 -0700614 // update ecmpSPG for all edge-pairs
615 for (EdgePair ep : edgePairs) {
616 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
617 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
618 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
619 }
Saurav Das6430f412018-01-25 09:49:01 -0800620
621 // here is where we update all devices not touched by this instance
622 updatedEcmpSpgMap.keySet().stream()
623 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
624 .filter(devId -> !updatedDevices.contains(devId))
625 .forEach(devId -> {
626 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
627 log.debug("Updating ECMPspg for remaining dev:{}", devId);
628 });
Saurav Das261c3002017-06-13 15:35:54 -0700629 return true;
630 }
631
632 /**
633 * Programs targetSw in the changedRoutes for given prefixes reachable by
634 * an edgePair. If no prefixes are given, the method will use configured
635 * subnets/prefixes. If some configured subnets belong only to a specific
636 * destination in the edgePair, then the target switch will be programmed
637 * only to that destination.
638 *
639 * @param edgePairs set of edge-pairs for which target will be programmed
640 * @param subnets a set of prefixes that need to be populated in the routing
641 * table of the target switch in the changedRoutes. Can be null,
642 * in which case all the configured prefixes belonging to the
643 * paired switches will be populated in the target switch
644 * @param changedRoutes a set of route-path changes, where each route-path is
645 * a list with its first element the src-switch (target)
646 * of the path, and the second element the dst-switch of
647 * the path.
648 * @return true if successful
649 */
650 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
651 Set<IpPrefix> subnets,
652 Set<ArrayList<DeviceId>> changedRoutes) {
653 for (EdgePair ep : edgePairs) {
654 // temp store for a target's changedRoutes to this edge-pair
655 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
656 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
657 while (i.hasNext()) {
658 ArrayList<DeviceId> route = i.next();
659 DeviceId dstSw = route.get(1);
660 if (ep.includes(dstSw)) {
661 // routeChange for edge pair found
662 // sort by target iff target is edge and remove from changedRoutes
663 DeviceId targetSw = route.get(0);
664 try {
665 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
666 continue;
667 }
668 } catch (DeviceConfigNotFoundException e) {
669 log.warn(e.getMessage() + "aborting redoRouting");
670 return false;
671 }
672 // route is from another edge to this edge-pair
673 if (targetRoutes.containsKey(targetSw)) {
674 targetRoutes.get(targetSw).add(route);
675 } else {
676 Set<ArrayList<DeviceId>> temp = new HashSet<>();
677 temp.add(route);
678 targetRoutes.put(targetSw, temp);
679 }
680 i.remove();
681 }
682 }
683 // so now for this edgepair we have a per target set of routechanges
684 // process target->edgePair route
685 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
686 targetRoutes.entrySet()) {
687 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
688 entry.getKey(), ep);
689 DeviceId targetSw = entry.getKey();
690 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
691 entry.getValue().forEach(route -> {
692 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
693 log.debug("route: target {} -> dst {} found with next-hops {}",
694 route.get(0), route.get(1), nhops);
695 perDstNextHops.put(route.get(1), nhops);
696 });
697 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
698 : subnets;
699 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
700 : subnets;
701 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
702 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Das6430f412018-01-25 09:49:01 -0800703 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
704 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das261c3002017-06-13 15:35:54 -0700705 // handle routing to subnets common to edge-pair
Saurav Das6430f412018-01-25 09:49:01 -0800706 // only if the targetSw is not part of the edge-pair and there
707 // exists a next hop to at least one of the devices in the edge-pair
708 if (!ep.includes(targetSw)
709 && ((nhDev1 != null && !nhDev1.isEmpty())
710 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das261c3002017-06-13 15:35:54 -0700711 if (!populateEcmpRoutingRulePartial(
712 targetSw,
713 ep.dev1, ep.dev2,
714 perDstNextHops,
715 Sets.intersection(ipDev1, ipDev2))) {
716 return false; // abort everything and fail fast
717 }
718 }
Saurav Das6430f412018-01-25 09:49:01 -0800719 // handle routing to subnets that only belong to dev1 only if
720 // a next-hop exists from the target to dev1
Saurav Das261c3002017-06-13 15:35:54 -0700721 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Das6430f412018-01-25 09:49:01 -0800722 if (!onlyDev1Subnets.isEmpty()
723 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700724 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800725 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das261c3002017-06-13 15:35:54 -0700726 if (!populateEcmpRoutingRulePartial(
727 targetSw,
728 ep.dev1, null,
729 onlyDev1NextHops,
730 onlyDev1Subnets)) {
731 return false; // abort everything and fail fast
732 }
733 }
Saurav Das6430f412018-01-25 09:49:01 -0800734 // handle routing to subnets that only belong to dev2 only if
735 // a next-hop exists from the target to dev2
Saurav Das261c3002017-06-13 15:35:54 -0700736 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Das6430f412018-01-25 09:49:01 -0800737 if (!onlyDev2Subnets.isEmpty()
738 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700739 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800740 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das261c3002017-06-13 15:35:54 -0700741 if (!populateEcmpRoutingRulePartial(
742 targetSw,
743 ep.dev2, null,
744 onlyDev2NextHops,
745 onlyDev2Subnets)) {
746 return false; // abort everything and fail fast
747 }
748 }
749 }
750 // if it gets here it has succeeded for all targets to this edge-pair
751 }
752 return true;
753 }
754
755 /**
756 * Programs targetSw in the changedRoutes for given prefixes reachable by
757 * a destination switch that is not part of an edge-pair.
758 * If no prefixes are given, the method will use configured subnets/prefixes.
759 *
760 * @param subnets a set of prefixes that need to be populated in the routing
761 * table of the target switch in the changedRoutes. Can be null,
762 * in which case all the configured prefixes belonging to the
763 * paired switches will be populated in the target switch
764 * @param changedRoutes a set of route-path changes, where each route-path is
765 * a list with its first element the src-switch (target)
766 * of the path, and the second element the dst-switch of
767 * the path.
768 * @return true if successful
769 */
770 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Das6430f412018-01-25 09:49:01 -0800771 Set<ArrayList<DeviceId>> changedRoutes,
772 Set<DeviceId> updatedDevices) {
Saurav Das261c3002017-06-13 15:35:54 -0700773 // aggregate route-path changes for each dst device
774 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
775 new HashMap<>();
776 for (ArrayList<DeviceId> route: changedRoutes) {
777 DeviceId dstSw = route.get(1);
778 ArrayList<ArrayList<DeviceId>> deviceRoutes =
779 routesBydevice.get(dstSw);
780 if (deviceRoutes == null) {
781 deviceRoutes = new ArrayList<>();
782 routesBydevice.put(dstSw, deviceRoutes);
783 }
784 deviceRoutes.add(route);
785 }
786 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
787 ArrayList<ArrayList<DeviceId>> deviceRoutes =
788 routesBydevice.get(impactedDstDevice);
789 for (ArrayList<DeviceId> route: deviceRoutes) {
790 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
791 route.get(0), route.get(1));
792 DeviceId targetSw = route.get(0);
793 DeviceId dstSw = route.get(1); // same as impactedDstDevice
794 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Das8e46aa72018-01-09 17:38:44 -0800795 if (nextHops.isEmpty()) {
Saurav Das68e1b6a2018-06-11 17:02:31 -0700796 log.debug("Could not find next hop from target:{} --> dst {} "
Saurav Das8e46aa72018-01-09 17:38:44 -0800797 + "skipping this route", targetSw, dstSw);
798 continue;
799 }
Saurav Das261c3002017-06-13 15:35:54 -0700800 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
801 nhops.put(dstSw, nextHops);
802 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
803 (subnets == null) ? Sets.newHashSet() : subnets)) {
804 return false; // abort routing and fail fast
805 }
806 log.debug("Populating flow rules from target: {} to dst: {}"
807 + " is successful", targetSw, dstSw);
808 }
809 //Only if all the flows for all impacted routes to a
810 //specific target are pushed successfully, update the
811 //ECMP graph for that target. Or else the next event
812 //would not see any changes in the ECMP graphs.
813 //In another case, the target switch has gone away, so
814 //routes can't be installed. In that case, the current map
815 //is updated here, without any flows being pushed.
816 currentEcmpSpgMap.put(impactedDstDevice,
817 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Das6430f412018-01-25 09:49:01 -0800818 updatedDevices.add(impactedDstDevice);
Saurav Das261c3002017-06-13 15:35:54 -0700819 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
820 }
821 return true;
822 }
823
824 /**
825 * Populate ECMP rules for subnets from target to destination via nexthops.
826 *
827 * @param targetSw Device ID of target switch in which rules will be programmed
828 * @param destSw1 Device ID of final destination switch to which the rules will forward
829 * @param destSw2 Device ID of paired destination switch to which the rules will forward
830 * A null deviceId indicates packets should only be sent to destSw1
Saurav Das97241862018-02-14 14:14:54 -0800831 * @param nextHops Map of a set of next hops per destSw
Saurav Das261c3002017-06-13 15:35:54 -0700832 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
833 * @return true if it succeeds in populating rules
834 */ // refactor
835 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
836 DeviceId destSw1,
837 DeviceId destSw2,
838 Map<DeviceId, Set<DeviceId>> nextHops,
839 Set<IpPrefix> subnets) {
840 boolean result;
841 // If both target switch and dest switch are edge routers, then set IP
842 // rule for both subnet and router IP.
843 boolean targetIsEdge;
844 boolean dest1IsEdge;
845 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
846 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
847
848 try {
849 targetIsEdge = config.isEdgeDevice(targetSw);
850 dest1IsEdge = config.isEdgeDevice(destSw1);
851 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
852 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
853 if (destSw2 != null) {
854 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
855 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
856 }
857 } catch (DeviceConfigNotFoundException e) {
858 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Das62ae6792017-05-15 15:34:25 -0700859 return false;
860 }
Saurav Das261c3002017-06-13 15:35:54 -0700861
862 if (targetIsEdge && dest1IsEdge) {
863 subnets = (subnets != null && !subnets.isEmpty())
864 ? Sets.newHashSet(subnets)
865 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Das97241862018-02-14 14:14:54 -0800866 // XXX - Rethink this - ignoring routerIPs in all other switches
867 // even edge to edge switches
Saurav Das261c3002017-06-13 15:35:54 -0700868 /*subnets.add(dest1RouterIpv4.toIpPrefix());
869 if (dest1RouterIpv6 != null) {
870 subnets.add(dest1RouterIpv6.toIpPrefix());
871 }
872 if (destSw2 != null && dest2RouterIpv4 != null) {
873 subnets.add(dest2RouterIpv4.toIpPrefix());
874 if (dest2RouterIpv6 != null) {
875 subnets.add(dest2RouterIpv6.toIpPrefix());
876 }
877 }*/
878 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
879 + "for subnets {}", targetSw, destSw1,
880 (destSw2 != null) ? ("& " + destSw2) : "",
881 subnets);
882 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
883 destSw1, destSw2,
884 nextHops);
885 if (!result) {
886 return false;
887 }
Saurav Das62ae6792017-05-15 15:34:25 -0700888 }
Saurav Das261c3002017-06-13 15:35:54 -0700889
890 if (!targetIsEdge && dest1IsEdge) {
891 // MPLS rules in all non-edge target devices. These rules are for
892 // individual destinations, even if the dsts are part of edge-pairs.
893 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
894 + "all MPLS rules", targetSw, destSw1);
895 result = rulePopulator.populateMplsRule(targetSw, destSw1,
896 nextHops.get(destSw1),
897 dest1RouterIpv4);
898 if (!result) {
899 return false;
900 }
901 if (dest1RouterIpv6 != null) {
Saurav Das97241862018-02-14 14:14:54 -0800902 int v4sid = 0, v6sid = 0;
903 try {
904 v4sid = config.getIPv4SegmentId(destSw1);
905 v6sid = config.getIPv6SegmentId(destSw1);
906 } catch (DeviceConfigNotFoundException e) {
907 log.warn(e.getMessage());
908 }
909 if (v4sid != v6sid) {
910 result = rulePopulator.populateMplsRule(targetSw, destSw1,
911 nextHops.get(destSw1),
912 dest1RouterIpv6);
913 if (!result) {
914 return false;
915 }
Saurav Das261c3002017-06-13 15:35:54 -0700916 }
917 }
918 }
919
Andreas Pantelopoulosfc4bc2a2018-03-12 16:30:20 -0700920 if (!targetIsEdge && !dest1IsEdge) {
921 // MPLS rules for inter-connected spines
922 // can be merged with above if, left it here for clarity
923 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
924 + "all MPLS rules", targetSw, destSw1);
925
926 result = rulePopulator.populateMplsRule(targetSw, destSw1,
927 nextHops.get(destSw1),
928 dest1RouterIpv4);
929 if (!result) {
930 return false;
931 }
932
933 if (dest1RouterIpv6 != null) {
934 int v4sid = 0, v6sid = 0;
935 try {
936 v4sid = config.getIPv4SegmentId(destSw1);
937 v6sid = config.getIPv6SegmentId(destSw1);
938 } catch (DeviceConfigNotFoundException e) {
939 log.warn(e.getMessage());
940 }
941 if (v4sid != v6sid) {
942 result = rulePopulator.populateMplsRule(targetSw, destSw1,
943 nextHops.get(destSw1),
944 dest1RouterIpv6);
945 if (!result) {
946 return false;
947 }
948 }
949 }
950 }
951
Saurav Das261c3002017-06-13 15:35:54 -0700952 // To save on ECMP groups
953 // avoid MPLS rules in non-edge-devices to non-edge-devices
954 // avoid MPLS transit rules in edge-devices
955 // avoid loopback IP rules in edge-devices to non-edge-devices
956 return true;
Saurav Das62ae6792017-05-15 15:34:25 -0700957 }
958
959 /**
960 * Processes a set a route-path changes by editing hash groups.
961 *
962 * @param routeChanges a set of route-path changes, where each route-path is
963 * a list with its first element the src-switch of the path
964 * and the second element the dst-switch of the path.
965 * @param linkOrSwitchFailed true if the route changes are for a failed
966 * switch or linkDown event
967 * @param failedSwitch the switchId if the route changes are for a failed switch,
968 * otherwise null
969 */
970 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
971 boolean linkOrSwitchFailed,
972 DeviceId failedSwitch) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700973 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
974 // first, ensure each routeChanges entry has two elements
Saurav Das62ae6792017-05-15 15:34:25 -0700975 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700976 if (route.size() == 1) {
977 // route-path changes are from everyone else to this switch
978 DeviceId dstSw = route.get(0);
979 srManager.deviceService.getAvailableDevices().forEach(sw -> {
980 if (!sw.id().equals(dstSw)) {
981 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
982 }
983 });
984 } else {
985 changedRoutes.add(route);
Saurav Das62ae6792017-05-15 15:34:25 -0700986 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700987 }
Saurav Das6430f412018-01-25 09:49:01 -0800988 boolean someFailed = false;
989 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700990 for (ArrayList<DeviceId> route : changedRoutes) {
991 DeviceId targetSw = route.get(0);
992 DeviceId dstSw = route.get(1);
Saurav Das62ae6792017-05-15 15:34:25 -0700993 if (linkOrSwitchFailed) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700994 boolean success = fixHashGroupsForRoute(route, true);
Saurav Das62ae6792017-05-15 15:34:25 -0700995 // it's possible that we cannot fix hash groups for a route
996 // if the target switch has failed. Nevertheless the ecmp graph
997 // for the impacted switch must still be updated.
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700998 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Das62ae6792017-05-15 15:34:25 -0700999 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1000 currentEcmpSpgMap.remove(targetSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001001 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Das62ae6792017-05-15 15:34:25 -07001002 + "target:{}", dstSw, targetSw);
Saurav Das6430f412018-01-25 09:49:01 -08001003 updatedDevices.add(targetSw);
1004 updatedDevices.add(dstSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001005 continue;
Saurav Das62ae6792017-05-15 15:34:25 -07001006 }
1007 //linkfailed - update both sides
Saurav Das62ae6792017-05-15 15:34:25 -07001008 if (success) {
1009 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001010 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Das6430f412018-01-25 09:49:01 -08001011 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1012 + " or switchdown", dstSw, targetSw);
1013 updatedDevices.add(targetSw);
1014 updatedDevices.add(dstSw);
1015 } else {
1016 someFailed = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001017 }
1018 } else {
1019 //linkup of seen before link
1020 boolean success = fixHashGroupsForRoute(route, false);
1021 if (success) {
1022 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1023 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1024 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Das62ae6792017-05-15 15:34:25 -07001025 targetSw, dstSw);
Saurav Das6430f412018-01-25 09:49:01 -08001026 updatedDevices.add(targetSw);
1027 updatedDevices.add(dstSw);
1028 } else {
1029 someFailed = true;
Saurav Das62ae6792017-05-15 15:34:25 -07001030 }
1031 }
1032 }
Saurav Das6430f412018-01-25 09:49:01 -08001033 if (!someFailed) {
1034 // here is where we update all devices not touched by this instance
1035 updatedEcmpSpgMap.keySet().stream()
1036 .filter(devId -> !updatedDevices.contains(devId))
1037 .forEach(devId -> {
1038 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1039 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1040 });
1041 }
Saurav Das62ae6792017-05-15 15:34:25 -07001042 }
1043
1044 /**
1045 * Edits hash groups in the src-switch (targetSw) of a route-path by
1046 * calling the groupHandler to either add or remove buckets in an existing
1047 * hash group.
1048 *
1049 * @param route a single list representing a route-path where the first element
1050 * is the src-switch (targetSw) of the route-path and the
1051 * second element is the dst-switch
1052 * @param revoke true if buckets in the hash-groups need to be removed;
1053 * false if buckets in the hash-groups need to be added
1054 * @return true if the hash group editing is successful
1055 */
1056 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1057 boolean revoke) {
1058 DeviceId targetSw = route.get(0);
1059 if (route.size() < 2) {
1060 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1061 return false;
1062 }
1063 DeviceId destSw = route.get(1);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001064 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001065 targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001066 // figure out the new next hops at the targetSw towards the destSw
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001067 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001068 // call group handler to change hash group at targetSw
1069 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1070 if (grpHandler == null) {
1071 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1072 + " {} hash group buckets for route:{} ", targetSw,
1073 (revoke) ? "revoke" : "repopulate", route);
1074 return false;
1075 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001076 log.debug("{} hash-groups buckets For Route {} -> {} to new next-hops {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001077 (revoke) ? "revoke" : "repopulating",
1078 targetSw, destSw, nextHops);
1079 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1080 destSw, true)
1081 : grpHandler.fixHashGroups(targetSw, nextHops,
1082 destSw, false);
1083 }
1084
1085 /**
Saurav Das261c3002017-06-13 15:35:54 -07001086 * Start the flow rule population process if it was never started. The
1087 * process finishes successfully when all flow rules are set and stops with
1088 * ABORTED status when any groups required for flows is not set yet.
Saurav Das62ae6792017-05-15 15:34:25 -07001089 */
Saurav Das261c3002017-06-13 15:35:54 -07001090 public void startPopulationProcess() {
1091 statusLock.lock();
1092 try {
1093 if (populationStatus == Status.IDLE
1094 || populationStatus == Status.SUCCEEDED
1095 || populationStatus == Status.ABORTED) {
1096 populateAllRoutingRules();
sangho28d0b6d2015-05-07 13:30:57 -07001097 } else {
Saurav Das261c3002017-06-13 15:35:54 -07001098 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1099 populationStatus);
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001100 }
Saurav Das261c3002017-06-13 15:35:54 -07001101 } finally {
1102 statusLock.unlock();
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001103 }
sanghofb7c7292015-04-13 15:15:58 -07001104 }
1105
Saurav Dasb149be12016-06-07 10:08:06 -07001106 /**
Saurav Das261c3002017-06-13 15:35:54 -07001107 * Revoke rules of given subnet in all edge switches.
1108 *
1109 * @param subnets subnet being removed
1110 * @return true if succeed
1111 */
1112 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1113 statusLock.lock();
1114 try {
Charles Chand66d6712018-03-29 16:03:41 -07001115 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1116 .map(Device::id)
1117 .filter(this::shouldProgram)
1118 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das261c3002017-06-13 15:35:54 -07001119 } finally {
1120 statusLock.unlock();
1121 }
1122 }
1123
1124 /**
Charles Chan910be6a2017-08-23 14:46:43 -07001125 * Populates IP rules for a route that has direct connection to the switch
1126 * if the current instance is the master of the switch.
1127 *
1128 * @param deviceId device ID of the device that next hop attaches to
1129 * @param prefix IP prefix of the route
1130 * @param hostMac MAC address of the next hop
1131 * @param hostVlanId Vlan ID of the nexthop
1132 * @param outPort port where the next hop attaches to
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001133 * @param directHost host is of type direct or indirect
Charles Chan910be6a2017-08-23 14:46:43 -07001134 */
1135 void populateRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001136 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chand66d6712018-03-29 16:03:41 -07001137 if (shouldProgram(deviceId)) {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001138 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan910be6a2017-08-23 14:46:43 -07001139 }
1140 }
1141
1142 /**
1143 * Removes IP rules for a route when the next hop is gone.
1144 * if the current instance is the master of the switch.
1145 *
1146 * @param deviceId device ID of the device that next hop attaches to
1147 * @param prefix IP prefix of the route
1148 * @param hostMac MAC address of the next hop
1149 * @param hostVlanId Vlan ID of the nexthop
1150 * @param outPort port that next hop attaches to
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001151 * @param directHost host is of type direct or indirect
Charles Chan910be6a2017-08-23 14:46:43 -07001152 */
1153 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001154 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chand66d6712018-03-29 16:03:41 -07001155 if (shouldProgram(deviceId)) {
Ruchi Sahota71bcb4e2019-01-28 01:08:18 +00001156 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan910be6a2017-08-23 14:46:43 -07001157 }
1158 }
1159
Charles Chand66d6712018-03-29 16:03:41 -07001160 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1161 if (shouldProgram(deviceId)) {
1162 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1163 }
1164 }
1165
1166 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1167 if (shouldProgram(deviceId)) {
1168 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1169 }
1170 }
1171
1172 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1173 VlanId vlanId, boolean popVlan, boolean install) {
1174 if (shouldProgram(deviceId)) {
1175 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1176 }
1177 }
1178
1179 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1180 VlanId vlanId, boolean popVlan, boolean install) {
1181 if (shouldProgram(deviceId)) {
1182 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1183 vlanId, popVlan, install);
1184 }
1185 }
1186
Charles Chan910be6a2017-08-23 14:46:43 -07001187 /**
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -07001188 * Populates IP rules for a route when the next hop is double-tagged.
1189 *
1190 * @param deviceId device ID that next hop attaches to
1191 * @param prefix IP prefix of the route
1192 * @param hostMac MAC address of the next hop
1193 * @param innerVlan Inner Vlan ID of the next hop
1194 * @param outerVlan Outer Vlan ID of the next hop
1195 * @param outerTpid Outer TPID of the next hop
1196 * @param outPort port that the next hop attaches to
1197 */
1198 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1199 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1200 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1201 VlanId dummyVlan = srManager.allocateDummyVlanId(
1202 new ConnectPoint(deviceId, outPort), prefix.address());
1203 if (!dummyVlan.equals(VlanId.NONE)) {
1204 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1205 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1206 srManager.routingRulePopulator.processDoubleTaggedFilter(
1207 deviceId, outPort, outerVlan, innerVlan, true);
1208 } else {
1209 log.error("Failed to allocate dummy VLAN ID for host {} at {}/{}",
1210 prefix.address(), deviceId, outPort);
1211 }
1212 }
1213 }
1214
1215 /**
1216 * Revokes IP rules for a route when the next hop is double-tagged.
1217 *
1218 * @param deviceId device ID that next hop attaches to
1219 * @param prefix IP prefix of the route
1220 * @param hostMac MAC address of the next hop
1221 * @param innerVlan Inner Vlan ID of the next hop
1222 * @param outerVlan Outer Vlan ID of the next hop
1223 * @param outerTpid Outer TPID of the next hop
1224 * @param outPort port that the next hop attaches to
1225 */
1226 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1227 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1228 // Revoke route either if this node have the mastership (when device is available) or
1229 // if this node is the leader (even when device is unavailable)
1230 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1231 if (srManager.deviceService.isAvailable(deviceId)) {
1232 // Master node will revoke specified rule.
1233 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1234 return;
1235 }
1236
1237 // isLocalMaster will return false when the device is unavailable.
1238 // Verify if this node is the leader in that case.
1239 NodeId leader = srManager.leadershipService.runForLeadership(
1240 deviceId.toString()).leaderNodeId();
1241 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1242 // Leader node will revoke specified rule.
1243 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1244 return;
1245 }
1246 }
1247
1248 VlanId dummyVlan = srManager.dummyVlanIdStore().get(new DummyVlanIdStoreKey(
1249 new ConnectPoint(deviceId, outPort), prefix.address()));
1250 if (dummyVlan == null) {
1251 log.error("Failed to get dummyVlanId for host {} at {}/{}.",
1252 prefix.address(), deviceId, outPort);
1253 } else {
1254 srManager.routingRulePopulator.revokeDoubleTaggedRoute(
1255 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1256 srManager.routingRulePopulator.processDoubleTaggedFilter(
1257 deviceId, outPort, outerVlan, innerVlan, false);
1258 }
1259 }
1260
1261
1262 /**
Saurav Das261c3002017-06-13 15:35:54 -07001263 * Remove ECMP graph entry for the given device. Typically called when
1264 * device is no longer available.
1265 *
1266 * @param deviceId the device for which graphs need to be purged
1267 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001268 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das6430f412018-01-25 09:49:01 -08001269 statusLock.lock();
1270 try {
Saurav Das6430f412018-01-25 09:49:01 -08001271 if (populationStatus == Status.STARTED) {
1272 log.warn("Previous rule population is not finished. Cannot"
1273 + " proceeed with purgeEcmpGraph for {}", deviceId);
1274 return;
1275 }
1276 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1277 currentEcmpSpgMap.remove(deviceId);
1278 if (updatedEcmpSpgMap != null) {
1279 updatedEcmpSpgMap.remove(deviceId);
1280 }
1281 } finally {
1282 statusLock.unlock();
Saurav Das261c3002017-06-13 15:35:54 -07001283 }
1284 }
1285
Saurav Das00e553b2018-04-21 17:19:48 -07001286 /**
1287 * Attempts a full reroute of route-paths if topology has changed relatively
1288 * close to a mastership change event. Does not do a reroute if mastership
1289 * change is due to reasons other than a ONOS cluster event - for example a
1290 * call to balance-masters, or a switch up/down event.
1291 *
1292 * @param devId the device identifier for which mastership has changed
1293 * @param me the mastership event
1294 */
1295 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1296 // give small delay to absorb mastership events that are caused by
1297 // device that has disconnected from cluster
Saurav Das49368392018-04-23 18:42:12 -07001298 executorServiceMstChg.schedule(new MasterChange(devId, me),
1299 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das00e553b2018-04-21 17:19:48 -07001300 }
1301
1302 protected final class MasterChange implements Runnable {
1303 private DeviceId devId;
1304 private MastershipEvent me;
1305 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1306 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
Saurav Dasec683dc2018-04-27 18:42:30 -07001307 private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
Saurav Das68e1b6a2018-06-11 17:02:31 -07001308 private static final long FULL_REROUTE_THRESHOLD = 10000; // ms
Saurav Das00e553b2018-04-21 17:19:48 -07001309
1310 MasterChange(DeviceId devId, MastershipEvent me) {
1311 this.devId = devId;
1312 this.me = me;
1313 }
1314
1315 @Override
1316 public void run() {
1317 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1318 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1319
1320 // ignore event for lost switch if cluster event hasn't happened -
1321 // device down event will handle it
1322 if ((me.roleInfo().master() == null
1323 || !srManager.deviceService.isAvailable(devId))
1324 && !clusterEvent) {
1325 log.debug("Full reroute not required for lost device: {}/{} "
1326 + "clusterEvent/timeSince: {}/{}",
1327 devId, me.roleInfo(), clusterEvent, lce);
1328 return;
1329 }
1330
1331 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1332 long lde = Instant.now().toEpochMilli() - update;
1333 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1334
1335 // ignore event for recently connected switch if cluster event hasn't
1336 // happened - link up events will handle it
1337 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1338 && !clusterEvent) {
1339 log.debug("Full reroute not required for recently available"
1340 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1341 + "clusterEvent/timeSince: {}/{}",
1342 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1343 return;
1344 }
1345
Saurav Dasec683dc2018-04-27 18:42:30 -07001346 long lepe = Instant.now().toEpochMilli()
1347 - srManager.lastEdgePortEvent.toEpochMilli();
1348 boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
1349
Saurav Das00e553b2018-04-21 17:19:48 -07001350 // if it gets here, then mastership change is likely due to onos
1351 // instance failure, or network partition in onos cluster
1352 // normally a mastership change like this does not require re-programming
1353 // but if topology changes happen at the same time then we may miss events
1354 if (!isRoutingStable() && clusterEvent) {
Saurav Dasec683dc2018-04-27 18:42:30 -07001355 log.warn("Mastership changed for dev: {}/{} while programming route-paths "
Saurav Das00e553b2018-04-21 17:19:48 -07001356 + "due to clusterEvent {} ms ago .. attempting full reroute",
1357 devId, me.roleInfo(), lce);
1358 if (srManager.mastershipService.isLocalMaster(devId)) {
1359 // old master could have died when populating filters
1360 populatePortAddressingRules(devId);
1361 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001362 // old master could have died when creating groups
Saurav Das00e553b2018-04-21 17:19:48 -07001363 // XXX right now we have no fine-grained way to only make changes
Saurav Das68e1b6a2018-06-11 17:02:31 -07001364 // for the route paths affected by this device. Thus we do a
1365 // full reroute after purging all hash groups. We also try to do
1366 // it only once, irrespective of the number of devices
1367 // that changed mastership when their master instance died.
1368 long lfrr = Instant.now().toEpochMilli() - lastFullReroute.toEpochMilli();
1369 boolean doFullReroute = lfrr > FULL_REROUTE_THRESHOLD;
1370 if (doFullReroute) {
1371 lastFullReroute = Instant.now();
1372 for (Device dev : srManager.deviceService.getDevices()) {
1373 if (shouldProgram(dev.id())) {
1374 srManager.purgeHashedNextObjectiveStore(dev.id());
1375 }
1376 }
1377 // give small delay to ensure entire store is purged
1378 executorServiceFRR.schedule(new FullRerouteAfterPurge(),
1379 PURGE_DELAY,
1380 TimeUnit.MILLISECONDS);
1381 } else {
1382 log.warn("Full reroute attempted {} ms ago .. skipping", lfrr);
1383 }
Saurav Dasec683dc2018-04-27 18:42:30 -07001384
1385 } else if (edgePortEvent && clusterEvent) {
1386 log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
1387 + "while edge-port event happened {} ms ago "
1388 + " .. reprogramming all edge-ports",
1389 devId, me.roleInfo(), lce, lepe);
1390 if (shouldProgram(devId)) {
1391 srManager.deviceService.getPorts(devId).stream()
1392 .filter(p -> srManager.interfaceService
1393 .isConfigured(new ConnectPoint(devId, p.number())))
1394 .forEach(p -> srManager.processPortUpdated(devId, p));
1395 }
1396
Saurav Das00e553b2018-04-21 17:19:48 -07001397 } else {
1398 log.debug("Stable route-paths .. full reroute not attempted for "
1399 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1400 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1401 deviceEvent, lde, clusterEvent, lce);
1402 }
1403 }
1404 }
1405
Saurav Das68e1b6a2018-06-11 17:02:31 -07001406 /**
1407 * Performs a full reroute of routing rules in all the switches. Assumes
1408 * caller has purged hash groups from the nextObjective store, otherwise
1409 * re-uses ones available in the store.
1410 */
1411 protected final class FullRerouteAfterPurge implements Runnable {
1412 @Override
1413 public void run() {
1414 populateAllRoutingRules();
1415 }
1416 }
1417
1418
Saurav Das261c3002017-06-13 15:35:54 -07001419 //////////////////////////////////////
1420 // Routing helper methods and classes
1421 //////////////////////////////////////
1422
1423 /**
Saurav Das68e1b6a2018-06-11 17:02:31 -07001424 * Computes set of affected routes due to failed link. Assumes previous ecmp
1425 * shortest-path graph exists for a switch in order to compute affected
1426 * routes. If such a graph does not exist, the method returns null.
Saurav Dasb149be12016-06-07 10:08:06 -07001427 *
1428 * @param linkFail the failed link
1429 * @return the set of affected routes which may be empty if no routes were
Saurav Das68e1b6a2018-06-11 17:02:31 -07001430 * affected
Saurav Dasb149be12016-06-07 10:08:06 -07001431 */
sanghofb7c7292015-04-13 15:15:58 -07001432 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sanghofb7c7292015-04-13 15:15:58 -07001433 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1434
1435 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001436 log.debug("Computing the impacted routes for device {} due to link fail",
1437 sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001438 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001439 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001440 continue;
1441 }
Charles Chand66d6712018-03-29 16:03:41 -07001442 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001443 // check for mastership change since last run
1444 if (!lastProgrammed.contains(sw.id())) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001445 log.warn("New responsibility for this node to program dev:{}"
Saurav Das00e553b2018-04-21 17:19:48 -07001446 + " ... nuking current ECMPspg", sw.id());
1447 currentEcmpSpgMap.remove(sw.id());
1448 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001449 lastProgrammed.add(sw.id());
1450
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001451 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1452 if (ecmpSpg == null) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001453 log.warn("No existing ECMP graph for switch {}. Assuming "
1454 + "all route-paths have changed towards it.", rootSw);
1455 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
1456 if (targetSw.equals(rootSw)) {
1457 continue;
1458 }
1459 routes.add(Lists.newArrayList(targetSw, rootSw));
1460 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1461 }
1462 continue;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001463 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001464
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001465 if (log.isDebugEnabled()) {
1466 log.debug("Root switch: {}", rootSw);
1467 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1468 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1469 }
1470 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1471 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1472 // figure out if the broken link affected any route-paths in this graph
1473 for (Integer itrIdx : switchVia.keySet()) {
1474 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1475 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1476 switchVia.get(itrIdx);
1477 for (DeviceId targetSw : swViaMap.keySet()) {
1478 log.trace("TargetSwitch {} --> RootSwitch {}",
1479 targetSw, rootSw);
Saurav Dasb149be12016-06-07 10:08:06 -07001480 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1481 log.trace(" Via:");
Pier Ventreadb4ae62016-11-23 09:57:42 -08001482 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb149be12016-06-07 10:08:06 -07001483 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001484 Set<ArrayList<DeviceId>> subLinks =
1485 computeLinks(targetSw, rootSw, swViaMap);
1486 for (ArrayList<DeviceId> alink: subLinks) {
1487 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1488 alink.get(1).equals(linkFail.dst().deviceId()))
1489 ||
1490 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1491 alink.get(1).equals(linkFail.src().deviceId()))) {
1492 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1493 ArrayList<DeviceId> aRoute = new ArrayList<>();
1494 aRoute.add(targetSw); // switch with rules to populate
1495 aRoute.add(rootSw); // towards this destination
1496 routes.add(aRoute);
1497 break;
1498 }
sanghofb7c7292015-04-13 15:15:58 -07001499 }
1500 }
1501 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001502
sanghofb7c7292015-04-13 15:15:58 -07001503 }
sangho28d0b6d2015-05-07 13:30:57 -07001504
sanghofb7c7292015-04-13 15:15:58 -07001505 }
sanghofb7c7292015-04-13 15:15:58 -07001506 return routes;
1507 }
1508
Saurav Das1b391d52016-11-29 14:27:25 -08001509 /**
1510 * Computes set of affected routes due to new links or failed switches.
1511 *
Saurav Dasdc7f2752018-03-18 21:28:15 -07001512 * @param failedSwitch deviceId of failed switch if any
Saurav Das1b391d52016-11-29 14:27:25 -08001513 * @return the set of affected routes which may be empty if no routes were
1514 * affected
1515 */
Saurav Dascea556f2018-03-05 14:37:16 -08001516 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das261c3002017-06-13 15:35:54 -07001517 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das1b391d52016-11-29 14:27:25 -08001518 ImmutableSet.builder();
sanghofb7c7292015-04-13 15:15:58 -07001519
1520 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das261c3002017-06-13 15:35:54 -07001521 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001522 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001523 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001524 continue;
1525 }
Charles Chand66d6712018-03-29 16:03:41 -07001526 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das261c3002017-06-13 15:35:54 -07001527 if (log.isTraceEnabled()) {
1528 log.trace("Device links for dev: {}", rootSw);
1529 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1530 log.trace("{} -> {} ", link.src().deviceId(),
1531 link.dst().deviceId());
1532 }
Saurav Dasb149be12016-06-07 10:08:06 -07001533 }
Saurav Das00e553b2018-04-21 17:19:48 -07001534 // check for mastership change since last run
1535 if (!lastProgrammed.contains(sw.id())) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001536 log.warn("New responsibility for this node to program dev:{}"
Saurav Das00e553b2018-04-21 17:19:48 -07001537 + " ... nuking current ECMPspg", sw.id());
1538 currentEcmpSpgMap.remove(sw.id());
1539 }
Saurav Das68e1b6a2018-06-11 17:02:31 -07001540 lastProgrammed.add(sw.id());
Saurav Das261c3002017-06-13 15:35:54 -07001541 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1542 if (currEcmpSpg == null) {
1543 log.debug("No existing ECMP graph for device {}.. adding self as "
1544 + "changed route", rootSw);
1545 changedRtBldr.add(Lists.newArrayList(rootSw));
1546 continue;
1547 }
1548 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Dasdebcf882018-04-06 20:16:01 -07001549 if (newEcmpSpg == null) {
1550 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1551 continue;
1552 }
Saurav Das261c3002017-06-13 15:35:54 -07001553 if (log.isDebugEnabled()) {
1554 log.debug("Root switch: {}", rootSw);
1555 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1556 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1557 }
1558 // first use the updated/new map to compare to current/existing map
1559 // as new links may have come up
1560 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1561 // then use the current/existing map to compare to updated/new map
1562 // as switch may have been removed
1563 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho28d0b6d2015-05-07 13:30:57 -07001564 }
Saurav Das1b391d52016-11-29 14:27:25 -08001565 }
sanghofb7c7292015-04-13 15:15:58 -07001566
Saurav Dascea556f2018-03-05 14:37:16 -08001567 // handle clearing state for a failed switch in case the switch does
1568 // not have a pair, or the pair is not available
1569 if (failedSwitch != null) {
Charles Chan6dbcd252018-04-02 11:46:38 -07001570 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1571 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dascea556f2018-03-05 14:37:16 -08001572 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1573 srManager.deviceService.getDevices().forEach(dev -> {
1574 if (!dev.id().equals(failedSwitch) &&
1575 srManager.mastershipService.isLocalMaster(dev.id())) {
1576 log.debug(" : {}", dev.id());
1577 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1578 }
1579 });
1580 }
1581 }
1582
Saurav Das261c3002017-06-13 15:35:54 -07001583 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das1b391d52016-11-29 14:27:25 -08001584 for (ArrayList<DeviceId> route: changedRoutes) {
1585 log.debug("Route changes Target -> Root");
1586 if (route.size() == 1) {
1587 log.debug(" : all -> {}", route.get(0));
1588 } else {
1589 log.debug(" : {} -> {}", route.get(0), route.get(1));
1590 }
1591 }
1592 return changedRoutes;
1593 }
1594
1595 /**
1596 * For the root switch, searches all the target nodes reachable in the base
1597 * graph, and compares paths to the ones in the comp graph.
1598 *
1599 * @param base the graph that is indexed for all reachable target nodes
1600 * from the root node
1601 * @param comp the graph that the base graph is compared to
1602 * @param rootSw both ecmp graphs are calculated for the root node
1603 * @return all the routes that have changed in the base graph
1604 */
1605 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1606 EcmpShortestPathGraph comp,
1607 DeviceId rootSw) {
1608 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1609 ImmutableSet.builder();
1610 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1611 base.getAllLearnedSwitchesAndVia();
1612 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1613 comp.getAllLearnedSwitchesAndVia();
1614 for (Integer itrIdx : baseMap.keySet()) {
1615 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1616 baseMap.get(itrIdx);
1617 for (DeviceId targetSw : baseViaMap.keySet()) {
1618 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1619 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1620 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001621 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das1b391d52016-11-29 14:27:25 -08001622 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das261c3002017-06-13 15:35:54 -07001623 route.add(targetSw); // switch with rules to populate
1624 route.add(rootSw); // towards this destination
Saurav Das1b391d52016-11-29 14:27:25 -08001625 changedRoutesBuilder.add(route);
sanghofb7c7292015-04-13 15:15:58 -07001626 }
1627 }
sangho28d0b6d2015-05-07 13:30:57 -07001628 }
Saurav Das1b391d52016-11-29 14:27:25 -08001629 return changedRoutesBuilder.build();
sanghofb7c7292015-04-13 15:15:58 -07001630 }
1631
Saurav Das261c3002017-06-13 15:35:54 -07001632 /**
1633 * Returns the ECMP paths traversed to reach the target switch.
1634 *
1635 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1636 * @param targetSw the switch to reach from the root switch
1637 * @return the nodes traversed on ECMP paths to the target switch
1638 */
sanghofb7c7292015-04-13 15:15:58 -07001639 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das1b391d52016-11-29 14:27:25 -08001640 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sanghofb7c7292015-04-13 15:15:58 -07001641 for (Integer itrIdx : switchVia.keySet()) {
1642 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1643 switchVia.get(itrIdx);
Saurav Das1b391d52016-11-29 14:27:25 -08001644 if (swViaMap.get(targetSw) == null) {
sanghofb7c7292015-04-13 15:15:58 -07001645 continue;
1646 } else {
Saurav Das1b391d52016-11-29 14:27:25 -08001647 return swViaMap.get(targetSw);
sanghofb7c7292015-04-13 15:15:58 -07001648 }
1649 }
1650
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001651 return null;
sanghofb7c7292015-04-13 15:15:58 -07001652 }
1653
Saurav Das261c3002017-06-13 15:35:54 -07001654 /**
1655 * Utility method to break down a path from src to dst device into a collection
1656 * of links.
1657 *
1658 * @param src src device of the path
1659 * @param dst dst device of the path
1660 * @param viaMap path taken from src to dst device
1661 * @return collection of links in the path
1662 */
sanghofb7c7292015-04-13 15:15:58 -07001663 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1664 DeviceId dst,
1665 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1666 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1667 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1668 DeviceId linkSrc = src;
1669 DeviceId linkDst = dst;
1670 for (DeviceId viaDevice: via) {
1671 ArrayList<DeviceId> link = new ArrayList<>();
1672 linkDst = viaDevice;
1673 link.add(linkSrc);
1674 link.add(linkDst);
1675 subLinks.add(link);
1676 linkSrc = viaDevice;
1677 }
1678 ArrayList<DeviceId> link = new ArrayList<>();
1679 link.add(linkSrc);
1680 link.add(dst);
1681 subLinks.add(link);
1682 }
1683
1684 return subLinks;
1685 }
1686
Charles Chanc22cef32016-04-29 14:38:22 -07001687 /**
Charles Chand66d6712018-03-29 16:03:41 -07001688 * Determines whether this controller instance should program the
Saurav Das261c3002017-06-13 15:35:54 -07001689 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chand66d6712018-03-29 16:03:41 -07001690 * <p>
1691 * Once an instance is elected, it will be the only instance responsible for programming
1692 * both devices in the pair until it goes down.
Charles Chanc22cef32016-04-29 14:38:22 -07001693 *
Saurav Das261c3002017-06-13 15:35:54 -07001694 * @param deviceId device identifier to consider for routing
Charles Chand66d6712018-03-29 16:03:41 -07001695 * @return true if current instance should handle the routing for given device
Charles Chanc22cef32016-04-29 14:38:22 -07001696 */
Charles Chand66d6712018-03-29 16:03:41 -07001697 boolean shouldProgram(DeviceId deviceId) {
Charles Chanfbcb8812018-04-18 18:41:05 -07001698 Boolean cached = shouldProgramCache.get(deviceId);
1699 if (cached != null) {
Saurav Das00e553b2018-04-21 17:19:48 -07001700 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chanfbcb8812018-04-18 18:41:05 -07001701 return cached;
1702 }
1703
Charles Chand66d6712018-03-29 16:03:41 -07001704 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sangho80f11cb2015-04-01 13:05:26 -07001705
Charles Chand66d6712018-03-29 16:03:41 -07001706 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1707 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1708 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
Saurav Das68e1b6a2018-06-11 17:02:31 -07001709 log.debug("Evaluate shouldProgram {}/pair={}. currentNodeId={}, master={}, pairMaster={}",
Charles Chand66d6712018-03-29 16:03:41 -07001710 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1711
1712 // No pair device configured. Only handle when current instance is the master of the device
1713 if (!pairDeviceId.isPresent()) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001714 log.debug("No pair device. currentNodeId={}, master={}", currentNodeId, masterNodeId);
Charles Chand66d6712018-03-29 16:03:41 -07001715 return currentNodeId.equals(masterNodeId);
sangho80f11cb2015-04-01 13:05:26 -07001716 }
Charles Chand66d6712018-03-29 16:03:41 -07001717
1718 // Should not handle if current instance is not the master of either switch
1719 if (!currentNodeId.equals(masterNodeId) &&
1720 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001721 log.debug("Current nodeId {} is neither the master of target device {} nor pair device {}",
Charles Chand66d6712018-03-29 16:03:41 -07001722 currentNodeId, deviceId, pairDeviceId);
1723 return false;
1724 }
1725
1726 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1727
1728 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1729 if (v == null) {
1730 // There is no value in the map. Elect a node
1731 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1732 } else {
1733 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1734 // Use the node in the map if it is still alive and is a master of any of the two switches
1735 return v;
1736 } else {
1737 // Previously elected node is no longer the master of either switch. Re-elect a node.
1738 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1739 }
1740 }
1741 }));
1742
1743 if (king != null) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001744 log.debug("{} is king, should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001745 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chand66d6712018-03-29 16:03:41 -07001746 return king.equals(currentNodeId);
1747 } else {
1748 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001749 shouldProgramCache.remove(deviceId);
Charles Chand66d6712018-03-29 16:03:41 -07001750 return false;
1751 }
1752 }
1753
1754 /**
1755 * Elects a node who should take responsibility of programming devices.
1756 * @param nodeIds list of candidate node ID
1757 *
1758 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1759 */
1760 private NodeId elect(List<NodeId> nodeIds) {
1761 // Remove all null elements. This could happen when some device has no master
1762 nodeIds.removeAll(Collections.singleton(null));
1763 nodeIds.sort(null);
1764 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1765 }
1766
Charles Chanfbcb8812018-04-18 18:41:05 -07001767 void invalidateShouldProgramCache(DeviceId deviceId) {
1768 shouldProgramCache.remove(deviceId);
1769 }
1770
Charles Chand66d6712018-03-29 16:03:41 -07001771 /**
1772 * Returns a set of device ID, containing given device and its pair device if exist.
1773 *
1774 * @param deviceId Device ID
1775 * @return a set of device ID, containing given device and its pair device if exist.
1776 */
1777 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1778 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1779 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1780 return ret;
sangho80f11cb2015-04-01 13:05:26 -07001781 }
1782
Charles Chanc22cef32016-04-29 14:38:22 -07001783 /**
Saurav Das261c3002017-06-13 15:35:54 -07001784 * Returns the set of deviceIds which are the next hops from the targetSw
1785 * to the dstSw according to the latest ECMP spg.
1786 *
1787 * @param targetSw the switch for which the next-hops are desired
1788 * @param dstSw the switch to which the next-hops lead to from the targetSw
1789 * @return set of next hop deviceIds, could be empty if no next hops are found
1790 */
1791 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1792 boolean targetIsEdge = false;
1793 try {
1794 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1795 } catch (DeviceConfigNotFoundException e) {
1796 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1797 + "continuing to getNextHops", targetSw);
1798 }
1799
1800 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1801 if (ecmpSpg == null) {
1802 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1803 return ImmutableSet.of();
1804 }
1805 HashMap<Integer,
1806 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1807 ecmpSpg.getAllLearnedSwitchesAndVia();
1808 for (Integer itrIdx : switchVia.keySet()) {
1809 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1810 switchVia.get(itrIdx);
1811 for (DeviceId target : swViaMap.keySet()) {
1812 if (!target.equals(targetSw)) {
1813 continue;
1814 }
Saurav Das49368392018-04-23 18:42:12 -07001815 // optimization for spines to not use leaves to get
1816 // to a spine or other leaves. Also leaves should not use other
1817 // leaves to get to the destination
1818 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Das97241862018-02-14 14:14:54 -08001819 boolean pathdevIsEdge = false;
1820 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Das49368392018-04-23 18:42:12 -07001821 log.debug("Evaluating next-hop in path: {}", via);
Saurav Das97241862018-02-14 14:14:54 -08001822 for (DeviceId pathdev : via) {
1823 try {
1824 pathdevIsEdge = srManager.deviceConfiguration
1825 .isEdgeDevice(pathdev);
1826 } catch (DeviceConfigNotFoundException e) {
1827 log.warn(e.getMessage());
1828 }
1829 if (pathdevIsEdge) {
Saurav Das68e1b6a2018-06-11 17:02:31 -07001830 log.debug("Avoiding {} hop path for targetSw:{}"
Saurav Das97241862018-02-14 14:14:54 -08001831 + " --> dstSw:{} which goes through an edge"
1832 + " device {} in path {}", itrIdx,
1833 targetSw, dstSw, pathdev, via);
1834 return ImmutableSet.of();
1835 }
1836 }
1837 }
Saurav Das261c3002017-06-13 15:35:54 -07001838 }
1839 Set<DeviceId> nextHops = new HashSet<>();
1840 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1841 if (via.isEmpty()) {
1842 // the dstSw is the next-hop from the targetSw
1843 nextHops.add(dstSw);
1844 } else {
1845 // first elem is next-hop in each ECMP path
1846 nextHops.add(via.get(0));
1847 }
1848 }
Saurav Das49368392018-04-23 18:42:12 -07001849 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
1850 dstSw, nextHops);
Saurav Das261c3002017-06-13 15:35:54 -07001851 return nextHops;
1852 }
1853 }
Saurav Das49368392018-04-23 18:42:12 -07001854 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das261c3002017-06-13 15:35:54 -07001855 return ImmutableSet.of(); //no next-hops found
1856 }
1857
Saurav Das261c3002017-06-13 15:35:54 -07001858 //////////////////////////////////////
1859 // Filtering rule creation
1860 //////////////////////////////////////
1861
1862 /**
Saurav Dasf9332192017-02-18 14:05:44 -08001863 * Populates filtering rules for port, and punting rules
1864 * for gateway IPs, loopback IPs and arp/ndp traffic.
1865 * Should only be called by the master instance for this device/port.
sangho80f11cb2015-04-01 13:05:26 -07001866 *
1867 * @param deviceId Switch ID to set the rules
1868 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001869 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das07c74602016-04-27 18:35:50 -07001870 // Although device is added, sometimes device store does not have the
1871 // ports for this device yet. It results in missing filtering rules in the
1872 // switch. We will attempt it a few times. If it still does not work,
1873 // user can manually repopulate using CLI command sr-reroute-network
Charles Chan18fa4252017-02-08 16:10:40 -08001874 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001875 if (firstRun == null) {
1876 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das07c74602016-04-27 18:35:50 -07001877 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001878 executorService.schedule(new RetryFilters(deviceId, firstRun),
1879 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sangho80f11cb2015-04-01 13:05:26 -07001880 }
1881
1882 /**
Saurav Dasd1872b02016-12-02 15:43:47 -08001883 * RetryFilters populates filtering objectives for a device and keeps retrying
1884 * till the number of ports filtered are constant for a predefined number
1885 * of attempts.
1886 */
1887 protected final class RetryFilters implements Runnable {
1888 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1889 DeviceId devId;
1890 int counter;
1891 PortFilterInfo prevRun;
1892
1893 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das07c74602016-04-27 18:35:50 -07001894 devId = deviceId;
Saurav Dasd1872b02016-12-02 15:43:47 -08001895 prevRun = previousRun;
1896 counter = 0;
Saurav Das07c74602016-04-27 18:35:50 -07001897 }
1898
1899 @Override
1900 public void run() {
Charles Chan077314e2017-06-22 14:27:17 -07001901 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chan18fa4252017-02-08 16:10:40 -08001902 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001903 boolean sameResult = prevRun.equals(thisRun);
1904 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1905 thisRun, sameResult);
Ray Milkey614352e2018-02-26 09:36:31 -08001906 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Dasf9332192017-02-18 14:05:44 -08001907 // exponentially increasing intervals for retries
1908 executorService.schedule(this,
1909 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1910 TimeUnit.MILLISECONDS);
Saurav Dasd1872b02016-12-02 15:43:47 -08001911 if (!sameResult) {
1912 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1913 }
Saurav Das07c74602016-04-27 18:35:50 -07001914 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001915 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das07c74602016-04-27 18:35:50 -07001916 }
Saurav Das07c74602016-04-27 18:35:50 -07001917 }
sangho80f11cb2015-04-01 13:05:26 -07001918}