blob: 7dfa632bf1b4f43536fb5b83f2e28c5aa9d8f8fb [file] [log] [blame]
sanghob35a6192015-04-01 13:05:26 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sanghob35a6192015-04-01 13:05:26 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.segmentrouting;
17
Saurav Dasc88d4662017-05-15 15:34:25 -070018import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableMap.Builder;
Charles Chan93e71ba2016-04-29 14:38:22 -070020import com.google.common.collect.ImmutableSet;
Saurav Das4e3224f2016-11-29 14:27:25 -080021import com.google.common.collect.Lists;
sangho20eff1d2015-04-13 15:15:58 -070022import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
Saurav Dasceccf242017-08-03 18:30:35 -070024
Jonghwan Hyun800d9d02018-04-09 09:40:50 -070025import org.onlab.packet.EthType;
sangho666cd6d2015-04-14 16:27:13 -070026import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080027import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070028import org.onlab.packet.IpPrefix;
Charles Chan2fde6d42017-08-23 14:46:43 -070029import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
Saurav Das7bcbe702017-06-13 15:35:54 -070031import org.onosproject.cluster.NodeId;
Saurav Das201762d2018-04-21 17:19:48 -070032import org.onosproject.mastership.MastershipEvent;
Charles Chan93e71ba2016-04-29 14:38:22 -070033import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070034import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070036import org.onosproject.net.Link;
Charles Chan2fde6d42017-08-23 14:46:43 -070037import org.onosproject.net.PortNumber;
Charles Chan0b4e6182015-11-03 10:42:14 -080038import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
39import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070040import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Jonghwan Hyun800d9d02018-04-09 09:40:50 -070041import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
Charles Chan2ff1bac2018-03-29 16:03:41 -070042import org.onosproject.store.serializers.KryoNamespaces;
43import org.onosproject.store.service.Serializer;
sanghob35a6192015-04-01 13:05:26 -070044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070047import java.time.Instant;
sanghob35a6192015-04-01 13:05:26 -070048import java.util.ArrayList;
Charles Chan2ff1bac2018-03-29 16:03:41 -070049import java.util.Collections;
sanghob35a6192015-04-01 13:05:26 -070050import java.util.HashMap;
51import java.util.HashSet;
Saurav Das7bcbe702017-06-13 15:35:54 -070052import java.util.Iterator;
Charles Chan2ff1bac2018-03-29 16:03:41 -070053import java.util.List;
Saurav Das7bcbe702017-06-13 15:35:54 -070054import java.util.Map;
Saurav Dasd2fded02016-12-02 15:43:47 -080055import java.util.Objects;
Charles Chanba6c5752018-04-02 11:46:38 -070056import java.util.Optional;
sanghob35a6192015-04-01 13:05:26 -070057import java.util.Set;
Saurav Das59232cf2016-04-27 18:35:50 -070058import java.util.concurrent.ScheduledExecutorService;
59import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090060import java.util.concurrent.locks.Lock;
61import java.util.concurrent.locks.ReentrantLock;
Saurav Das604ab3a2018-03-18 21:28:15 -070062import java.util.stream.Stream;
63
Pier Ventree0ae7a32016-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;
sanghob35a6192015-04-01 13:05:26 -070067
Charles Chane849c192016-01-11 18:28:54 -080068/**
69 * Default routing handler that is responsible for route computing and
70 * routing rule population.
71 */
sanghob35a6192015-04-01 13:05:26 -070072public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080073 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey3717e602018-02-01 13:49:47 -080074 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Das018605f2017-02-18 14:05:44 -080075 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070076 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das201762d2018-04-21 17:19:48 -070077 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Saurav Das137f27f2018-06-11 17:02:31 -070078 private static final long PURGE_DELAY = 1000; // ms
Charles Chan93e71ba2016-04-29 14:38:22 -070079 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070080
81 private SegmentRoutingManager srManager;
82 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053083 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
84 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070085 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090086 private final Lock statusLock = new ReentrantLock();
87 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070088 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080089 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Dasc6ff8f02018-04-23 18:42:12 -070090 private ScheduledExecutorService executorServiceMstChg
91 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
Saurav Das137f27f2018-06-11 17:02:31 -070092 private ScheduledExecutorService executorServiceFRR
93 = newScheduledThreadPool(1, groupedThreads("fullRR", "fullRR-%d", log));
Saurav Dasc6ff8f02018-04-23 18:42:12 -070094
Saurav Das201762d2018-04-21 17:19:48 -070095 private Instant lastRoutingChange = Instant.EPOCH;
Saurav Das137f27f2018-06-11 17:02:31 -070096 private Instant lastFullReroute = Instant.EPOCH;
sanghob35a6192015-04-01 13:05:26 -070097
Saurav Das201762d2018-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 Chan2ff1bac2018-03-29 16:03:41 -0700100 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chan50bb6ef2018-04-18 18:41:05 -0700101 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chan2ff1bac2018-03-29 16:03:41 -0700102
Saurav Das201762d2018-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
sanghob35a6192015-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 Vavilapallif5b234a2015-04-21 13:04:13 -0700118 // population process was aborted due to errors, mostly for groups not
119 // found.
sanghob35a6192015-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 Chan2ff1bac2018-03-29 16:03:41 -0700131 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chan50bb6ef2018-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) {
sanghob35a6192015-04-01 13:05:26 -0700147 this.srManager = srManager;
148 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700149 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700150 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700151 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das201762d2018-04-21 17:19:48 -0700152 this.lastProgrammed = Sets.newConcurrentHashSet();
sanghob35a6192015-04-01 13:05:26 -0700153 }
154
155 /**
Saurav Dasc88d4662017-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 Das7bcbe702017-06-13 15:35:54 -0700159 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-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 Dasceccf242017-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 HIGUCHI0c47d532017-08-18 23:16:35 -0700193 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
194 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700195 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700196 return (now - last) > STABLITY_THRESHOLD;
197 }
198
Saurav Dasc6ff8f02018-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 Das137f27f2018-06-11 17:02:31 -0700206 executorServiceFRR.shutdown();
Saurav Dasc6ff8f02018-04-23 18:42:12 -0700207 }
Saurav Dasceccf242017-08-03 18:30:35 -0700208
Saurav Das7bcbe702017-06-13 15:35:54 -0700209 //////////////////////////////////////
210 // Route path handling
211 //////////////////////////////////////
212
Saurav Das45f48152018-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 Das7bcbe702017-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 Dasc88d4662017-05-15 15:34:25 -0700220 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700221 * Populates all routing rules to all switches. Typically triggered at
222 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700223 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700224 public void populateAllRoutingRules() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700225 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900226 statusLock.lock();
227 try {
Saurav Das7bcbe702017-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 Yuta84a25fc2015-09-08 16:16:31 +0900234 populationStatus = Status.STARTED;
235 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700236 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900237 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700238
Saurav Das7bcbe702017-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 Hart8ca2bc02017-11-30 18:23:42 -0800243 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700244 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800245 new EcmpShortestPathGraph(dstSw, srManager);
246 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700247 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
248 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700249 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-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 Das7bcbe702017-06-13 15:35:54 -0700253 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700254
255 if (!shouldProgram(dstSw)) {
Saurav Das201762d2018-04-21 17:19:48 -0700256 lastProgrammed.remove(dstSw);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900257 continue;
Saurav Das201762d2018-04-21 17:19:48 -0700258 } else {
259 lastProgrammed.add(dstSw);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900260 }
Saurav Das201762d2018-04-21 17:19:48 -0700261 // To do a full reroute, assume all route-paths have changed
Charles Chan2ff1bac2018-03-29 16:03:41 -0700262 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800263 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
264 if (targetSw.equals(dev)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700265 continue;
266 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800267 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700268 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900269 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700270 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900271
Saurav Das7bcbe702017-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;
sanghob35a6192015-04-01 13:05:26 -0700277 }
278
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900279 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
280 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700281 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900282 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700283 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900284 } finally {
285 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700286 }
sanghob35a6192015-04-01 13:05:26 -0700287 }
288
sangho20eff1d2015-04-13 15:15:58 -0700289 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700290 * Populate rules from all other edge devices to the connect-point(s)
291 * specified for the given subnets.
292 *
293 * @param cpts connect point(s) of the subnets being added
294 * @param subnets subnets being added
Charles Chan2fde6d42017-08-23 14:46:43 -0700295 */
296 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700297 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan71e64f12017-09-11 15:21:57 -0700298 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
299 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
300 return;
301 }
302
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700303 lastRoutingChange = Instant.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700304 statusLock.lock();
305 try {
306 if (populationStatus == Status.STARTED) {
307 log.warn("Previous rule population is not finished. Cannot"
308 + " proceed with routing rules for added routes");
309 return;
310 }
311 populationStatus = Status.STARTED;
312 rulePopulator.resetCounter();
Charles Chan2fde6d42017-08-23 14:46:43 -0700313 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
314 subnets, cpts);
Saurav Dasc568c342018-01-25 09:49:01 -0800315 // In principle an update to a subnet/prefix should not require a
316 // new ECMPspg calculation as it is not a topology event. As a
317 // result, we use the current/existing ECMPspg in the updated map
318 // used by the redoRouting method.
Saurav Das15a81782018-02-09 09:15:03 -0800319 if (updatedEcmpSpgMap == null) {
320 updatedEcmpSpgMap = new HashMap<>();
321 }
Saurav Dasc568c342018-01-25 09:49:01 -0800322 currentEcmpSpgMap.entrySet().forEach(entry -> {
323 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase7f51012018-02-09 17:26:45 -0800324 if (log.isTraceEnabled()) {
325 log.trace("Root switch: {}", entry.getKey());
326 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Dasc568c342018-01-25 09:49:01 -0800327 }
328 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700329 Set<EdgePair> edgePairs = new HashSet<>();
330 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
331 boolean handleRouting = false;
332
333 if (cpts.size() == 2) {
334 // ensure connect points are edge-pairs
335 Iterator<ConnectPoint> iter = cpts.iterator();
336 DeviceId dev1 = iter.next().deviceId();
Charles Chanba6c5752018-04-02 11:46:38 -0700337 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
338 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
339 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700340 } else {
341 log.warn("Connectpoints {} for subnets {} not on "
342 + "pair-devices.. aborting populateSubnet", cpts, subnets);
343 populationStatus = Status.ABORTED;
344 return;
345 }
346 for (ConnectPoint cp : cpts) {
Saurav Dasc568c342018-01-25 09:49:01 -0800347 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
348 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700349 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800350 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
351 log.warn("populateSubnet: no updated graph for dev:{}"
352 + " ... creating", cp.deviceId());
353 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700354 if (!shouldProgram(cp.deviceId())) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700355 continue;
356 }
357 handleRouting = true;
358 }
359 } else {
360 // single connect point
361 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Dasc568c342018-01-25 09:49:01 -0800362 if (updatedEcmpSpgMap.get(dstSw) == null) {
363 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700364 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800365 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
366 log.warn("populateSubnet: no updated graph for dev:{}"
367 + " ... creating", dstSw);
368 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700369 handleRouting = shouldProgram(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700370 }
371
372 if (!handleRouting) {
373 log.debug("This instance is not handling ecmp routing to the "
374 + "connectPoint(s) {}", cpts);
375 populationStatus = Status.ABORTED;
376 return;
377 }
378
379 // if it gets here, this instance should handle routing for the
380 // connectpoint(s). Assume all route-paths have to be updated to
381 // the connectpoint(s) with the following exceptions
382 // 1. if target is non-edge no need for routing rules
383 // 2. if target is one of the connectpoints
384 for (ConnectPoint cp : cpts) {
385 DeviceId dstSw = cp.deviceId();
386 for (Device targetSw : srManager.deviceService.getDevices()) {
387 boolean isEdge = false;
388 try {
389 isEdge = config.isEdgeDevice(targetSw.id());
390 } catch (DeviceConfigNotFoundException e) {
Charles Chan92726132018-02-16 17:20:54 -0800391 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
392 continue;
Saurav Das7bcbe702017-06-13 15:35:54 -0700393 }
Charles Chanba6c5752018-04-02 11:46:38 -0700394 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700395 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chanba6c5752018-04-02 11:46:38 -0700396 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700397 continue;
398 }
399 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
400 }
401 }
402
403 if (!redoRouting(routeChanges, edgePairs, subnets)) {
404 log.debug("populateSubnet: populationStatus is ABORTED");
405 populationStatus = Status.ABORTED;
406 log.warn("Failed to repopulate the rules for subnet.");
407 return;
408 }
409
410 log.debug("populateSubnet: populationStatus is SUCCEEDED");
411 populationStatus = Status.SUCCEEDED;
412 log.info("Completed subnet population. Total # of rules pushed : {}",
413 rulePopulator.getCounter());
414 return;
415
416 } finally {
417 statusLock.unlock();
418 }
419 }
420
421 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700422 * Populates the routing rules or makes hash group changes according to the
423 * route-path changes due to link failure, switch failure or link up. This
424 * method should only be called for one of these three possible event-types.
Saurav Das604ab3a2018-03-18 21:28:15 -0700425 * Note that when a switch goes away, all of its links fail as well, but
426 * this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700427 *
Saurav Das604ab3a2018-03-18 21:28:15 -0700428 * @param linkDown the single failed link, or null for other conditions such
429 * as link-up or a removed switch
Saurav Dasc88d4662017-05-15 15:34:25 -0700430 * @param linkUp the single link up, or null for other conditions such as
Saurav Das604ab3a2018-03-18 21:28:15 -0700431 * link-down or a removed switch
432 * @param switchDown the removed switch, or null for other conditions such
433 * as link-down or link-up
434 * @param seenBefore true if this event is for a linkUp or linkDown for a
435 * seen link
436 */
437 // TODO This method should be refactored into three separated methods
Charles Chan5adc6282018-06-19 20:56:33 -0700438 public void populateRoutingRulesForLinkStatusChange(Link linkDown, Link linkUp,
439 DeviceId switchDown, boolean seenBefore) {
Saurav Das604ab3a2018-03-18 21:28:15 -0700440 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
441 .count() != 1) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700442 log.warn("Only one event can be handled for link status change .. aborting");
443 return;
444 }
Saurav Das604ab3a2018-03-18 21:28:15 -0700445
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700446 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900447 statusLock.lock();
448 try {
sangho20eff1d2015-04-13 15:15:58 -0700449
450 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700451 log.warn("Previous rule population is not finished. Cannot"
Saurav Dasc568c342018-01-25 09:49:01 -0800452 + " proceeed with routingRules for Topology change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700453 return;
sangho20eff1d2015-04-13 15:15:58 -0700454 }
455
Saurav Das7bcbe702017-06-13 15:35:54 -0700456 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700457 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700458 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700459 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530460 EcmpShortestPathGraph ecmpSpgUpdated =
461 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700462 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700463 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
464 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700465 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700466 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
467 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
468 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700469 }
sangho45b009c2015-05-07 13:30:57 -0700470 }
471
Saurav Dasc568c342018-01-25 09:49:01 -0800472 log.info("Starting to populate routing rules from Topology change");
sangho52abe3a2015-05-05 14:13:34 -0700473
sangho20eff1d2015-04-13 15:15:58 -0700474 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700475 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700476 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700477 populationStatus = Status.STARTED;
Saurav Dasc568c342018-01-25 09:49:01 -0800478 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
479 boolean hashGroupsChanged = false;
Saurav Das4e3224f2016-11-29 14:27:25 -0800480 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700481 if (linkDown == null) {
482 // either a linkUp or a switchDown - compute all route changes by
483 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dase0d4c872018-03-05 14:37:16 -0800484 routeChanges = computeRouteChange(switchDown);
Saurav Dasc88d4662017-05-15 15:34:25 -0700485
Saurav Das9df5b7c2017-08-14 16:44:43 -0700486 // deal with linkUp of a seen-before link
Saurav Das604ab3a2018-03-18 21:28:15 -0700487 if (linkUp != null && seenBefore) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700488 // link previously seen before
489 // do hash-bucket changes instead of a re-route
490 processHashGroupChange(routeChanges, false, null);
491 // clear out routesChanges so a re-route is not attempted
492 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800493 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700494 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700495 // for a linkUp of a never-seen-before link
496 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700497
Saurav Das9df5b7c2017-08-14 16:44:43 -0700498 //deal with switchDown
499 if (switchDown != null) {
500 processHashGroupChange(routeChanges, true, switchDown);
501 // clear out routesChanges so a re-route is not attempted
502 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800503 hashGroupsChanged = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700504 }
sangho20eff1d2015-04-13 15:15:58 -0700505 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700506 // link has gone down
507 // Compare existing ECMP SPG only with the link that went down
508 routeChanges = computeDamagedRoutes(linkDown);
Saurav Das137f27f2018-06-11 17:02:31 -0700509 processHashGroupChange(routeChanges, true, null);
510 // clear out routesChanges so a re-route is not attempted
511 routeChanges = ImmutableSet.of();
512 hashGroupsChanged = true;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700513 }
514
sangho20eff1d2015-04-13 15:15:58 -0700515 if (routeChanges.isEmpty()) {
Saurav Dasc568c342018-01-25 09:49:01 -0800516 if (hashGroupsChanged) {
517 log.info("Hash-groups changed for link status change");
518 } else {
519 log.info("No re-route or re-hash attempted for the link"
520 + " status change");
521 updatedEcmpSpgMap.keySet().forEach(devId -> {
522 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
523 log.debug("Updating ECMPspg for remaining dev:{}", devId);
524 });
525 }
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700526 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700527 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700528 return;
sangho20eff1d2015-04-13 15:15:58 -0700529 }
530
Saurav Dasc88d4662017-05-15 15:34:25 -0700531 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700532 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700533 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700534 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700535 log.info("Completed repopulation of rules for link-status change."
536 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700537 return;
sangho20eff1d2015-04-13 15:15:58 -0700538 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700539 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700540 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700541 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700542 return;
sangho20eff1d2015-04-13 15:15:58 -0700543 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900544 } finally {
545 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700546 }
547 }
548
Saurav Dasc88d4662017-05-15 15:34:25 -0700549 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700550 * Processes a set a route-path changes by reprogramming routing rules and
551 * creating new hash-groups or editing them if necessary. This method also
552 * determines the next-hops for the route-path from the src-switch (target)
553 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700554 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700555 * @param routeChanges a set of route-path changes, where each route-path is
556 * a list with its first element the src-switch (target)
557 * of the path, and the second element the dst-switch of
558 * the path.
559 * @param edgePairs a set of edge-switches that are paired by configuration
560 * @param subnets a set of prefixes that need to be populated in the routing
561 * table of the target switch in the route-path. Can be null,
562 * in which case all the prefixes belonging to the dst-switch
563 * will be populated in the target switch
564 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700565 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700566 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
567 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
568 // first make every entry two-elements
569 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
570 for (ArrayList<DeviceId> route : routeChanges) {
571 if (route.size() == 1) {
572 DeviceId dstSw = route.get(0);
573 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
574 if (ec == null) {
575 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
576 return false;
577 }
578 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
579 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
580 changedRoutes.add(Lists.newArrayList(target, dstSw));
581 });
582 });
583 } else {
584 DeviceId targetSw = route.get(0);
585 DeviceId dstSw = route.get(1);
586 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
587 }
588 }
589
590 // now process changedRoutes according to edgePairs
591 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
592 return false; //abort routing and fail fast
593 }
594
595 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Dasc568c342018-01-25 09:49:01 -0800596 Set<DeviceId> updatedDevices = Sets.newHashSet();
597 if (!redoRoutingIndividualDests(subnets, changedRoutes,
598 updatedDevices)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700599 return false; //abort routing and fail fast
600 }
601
Saurav Das7bcbe702017-06-13 15:35:54 -0700602 // update ecmpSPG for all edge-pairs
603 for (EdgePair ep : edgePairs) {
604 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
605 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
606 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
607 }
Saurav Dasc568c342018-01-25 09:49:01 -0800608
609 // here is where we update all devices not touched by this instance
610 updatedEcmpSpgMap.keySet().stream()
611 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
612 .filter(devId -> !updatedDevices.contains(devId))
613 .forEach(devId -> {
614 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
615 log.debug("Updating ECMPspg for remaining dev:{}", devId);
616 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700617 return true;
618 }
619
620 /**
621 * Programs targetSw in the changedRoutes for given prefixes reachable by
622 * an edgePair. If no prefixes are given, the method will use configured
623 * subnets/prefixes. If some configured subnets belong only to a specific
624 * destination in the edgePair, then the target switch will be programmed
625 * only to that destination.
626 *
627 * @param edgePairs set of edge-pairs for which target will be programmed
628 * @param subnets a set of prefixes that need to be populated in the routing
629 * table of the target switch in the changedRoutes. Can be null,
630 * in which case all the configured prefixes belonging to the
631 * paired switches will be populated in the target switch
632 * @param changedRoutes a set of route-path changes, where each route-path is
633 * a list with its first element the src-switch (target)
634 * of the path, and the second element the dst-switch of
635 * the path.
636 * @return true if successful
637 */
638 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
639 Set<IpPrefix> subnets,
640 Set<ArrayList<DeviceId>> changedRoutes) {
641 for (EdgePair ep : edgePairs) {
642 // temp store for a target's changedRoutes to this edge-pair
643 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
644 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
645 while (i.hasNext()) {
646 ArrayList<DeviceId> route = i.next();
647 DeviceId dstSw = route.get(1);
648 if (ep.includes(dstSw)) {
649 // routeChange for edge pair found
650 // sort by target iff target is edge and remove from changedRoutes
651 DeviceId targetSw = route.get(0);
652 try {
653 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
654 continue;
655 }
656 } catch (DeviceConfigNotFoundException e) {
657 log.warn(e.getMessage() + "aborting redoRouting");
658 return false;
659 }
660 // route is from another edge to this edge-pair
661 if (targetRoutes.containsKey(targetSw)) {
662 targetRoutes.get(targetSw).add(route);
663 } else {
664 Set<ArrayList<DeviceId>> temp = new HashSet<>();
665 temp.add(route);
666 targetRoutes.put(targetSw, temp);
667 }
668 i.remove();
669 }
670 }
671 // so now for this edgepair we have a per target set of routechanges
672 // process target->edgePair route
673 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
674 targetRoutes.entrySet()) {
675 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
676 entry.getKey(), ep);
677 DeviceId targetSw = entry.getKey();
678 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
679 entry.getValue().forEach(route -> {
680 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
681 log.debug("route: target {} -> dst {} found with next-hops {}",
682 route.get(0), route.get(1), nhops);
683 perDstNextHops.put(route.get(1), nhops);
684 });
685 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
686 : subnets;
687 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
688 : subnets;
689 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
690 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Dasc568c342018-01-25 09:49:01 -0800691 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
692 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700693 // handle routing to subnets common to edge-pair
Saurav Dasc568c342018-01-25 09:49:01 -0800694 // only if the targetSw is not part of the edge-pair and there
695 // exists a next hop to at least one of the devices in the edge-pair
696 if (!ep.includes(targetSw)
697 && ((nhDev1 != null && !nhDev1.isEmpty())
698 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700699 if (!populateEcmpRoutingRulePartial(
700 targetSw,
701 ep.dev1, ep.dev2,
702 perDstNextHops,
703 Sets.intersection(ipDev1, ipDev2))) {
704 return false; // abort everything and fail fast
705 }
706 }
Saurav Dasc568c342018-01-25 09:49:01 -0800707 // handle routing to subnets that only belong to dev1 only if
708 // a next-hop exists from the target to dev1
Saurav Das7bcbe702017-06-13 15:35:54 -0700709 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Dasc568c342018-01-25 09:49:01 -0800710 if (!onlyDev1Subnets.isEmpty()
711 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700712 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800713 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das7bcbe702017-06-13 15:35:54 -0700714 if (!populateEcmpRoutingRulePartial(
715 targetSw,
716 ep.dev1, null,
717 onlyDev1NextHops,
718 onlyDev1Subnets)) {
719 return false; // abort everything and fail fast
720 }
721 }
Saurav Dasc568c342018-01-25 09:49:01 -0800722 // handle routing to subnets that only belong to dev2 only if
723 // a next-hop exists from the target to dev2
Saurav Das7bcbe702017-06-13 15:35:54 -0700724 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Dasc568c342018-01-25 09:49:01 -0800725 if (!onlyDev2Subnets.isEmpty()
726 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700727 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800728 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700729 if (!populateEcmpRoutingRulePartial(
730 targetSw,
731 ep.dev2, null,
732 onlyDev2NextHops,
733 onlyDev2Subnets)) {
734 return false; // abort everything and fail fast
735 }
736 }
737 }
738 // if it gets here it has succeeded for all targets to this edge-pair
739 }
740 return true;
741 }
742
743 /**
744 * Programs targetSw in the changedRoutes for given prefixes reachable by
745 * a destination switch that is not part of an edge-pair.
746 * If no prefixes are given, the method will use configured subnets/prefixes.
747 *
748 * @param subnets a set of prefixes that need to be populated in the routing
749 * table of the target switch in the changedRoutes. Can be null,
750 * in which case all the configured prefixes belonging to the
751 * paired switches will be populated in the target switch
752 * @param changedRoutes a set of route-path changes, where each route-path is
753 * a list with its first element the src-switch (target)
754 * of the path, and the second element the dst-switch of
755 * the path.
756 * @return true if successful
757 */
758 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Dasc568c342018-01-25 09:49:01 -0800759 Set<ArrayList<DeviceId>> changedRoutes,
760 Set<DeviceId> updatedDevices) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700761 // aggregate route-path changes for each dst device
762 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
763 new HashMap<>();
764 for (ArrayList<DeviceId> route: changedRoutes) {
765 DeviceId dstSw = route.get(1);
766 ArrayList<ArrayList<DeviceId>> deviceRoutes =
767 routesBydevice.get(dstSw);
768 if (deviceRoutes == null) {
769 deviceRoutes = new ArrayList<>();
770 routesBydevice.put(dstSw, deviceRoutes);
771 }
772 deviceRoutes.add(route);
773 }
774 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
775 ArrayList<ArrayList<DeviceId>> deviceRoutes =
776 routesBydevice.get(impactedDstDevice);
777 for (ArrayList<DeviceId> route: deviceRoutes) {
778 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
779 route.get(0), route.get(1));
780 DeviceId targetSw = route.get(0);
781 DeviceId dstSw = route.get(1); // same as impactedDstDevice
782 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Dasbd071d82018-01-09 17:38:44 -0800783 if (nextHops.isEmpty()) {
Saurav Das137f27f2018-06-11 17:02:31 -0700784 log.debug("Could not find next hop from target:{} --> dst {} "
Saurav Dasbd071d82018-01-09 17:38:44 -0800785 + "skipping this route", targetSw, dstSw);
786 continue;
787 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700788 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
789 nhops.put(dstSw, nextHops);
790 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
791 (subnets == null) ? Sets.newHashSet() : subnets)) {
792 return false; // abort routing and fail fast
793 }
794 log.debug("Populating flow rules from target: {} to dst: {}"
795 + " is successful", targetSw, dstSw);
796 }
797 //Only if all the flows for all impacted routes to a
798 //specific target are pushed successfully, update the
799 //ECMP graph for that target. Or else the next event
800 //would not see any changes in the ECMP graphs.
801 //In another case, the target switch has gone away, so
802 //routes can't be installed. In that case, the current map
803 //is updated here, without any flows being pushed.
804 currentEcmpSpgMap.put(impactedDstDevice,
805 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Dasc568c342018-01-25 09:49:01 -0800806 updatedDevices.add(impactedDstDevice);
Saurav Das7bcbe702017-06-13 15:35:54 -0700807 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
808 }
809 return true;
810 }
811
812 /**
813 * Populate ECMP rules for subnets from target to destination via nexthops.
814 *
815 * @param targetSw Device ID of target switch in which rules will be programmed
816 * @param destSw1 Device ID of final destination switch to which the rules will forward
817 * @param destSw2 Device ID of paired destination switch to which the rules will forward
818 * A null deviceId indicates packets should only be sent to destSw1
Saurav Dasa4020382018-02-14 14:14:54 -0800819 * @param nextHops Map of a set of next hops per destSw
Saurav Das7bcbe702017-06-13 15:35:54 -0700820 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
821 * @return true if it succeeds in populating rules
822 */ // refactor
823 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
824 DeviceId destSw1,
825 DeviceId destSw2,
826 Map<DeviceId, Set<DeviceId>> nextHops,
827 Set<IpPrefix> subnets) {
828 boolean result;
829 // If both target switch and dest switch are edge routers, then set IP
830 // rule for both subnet and router IP.
831 boolean targetIsEdge;
832 boolean dest1IsEdge;
833 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
834 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
835
836 try {
837 targetIsEdge = config.isEdgeDevice(targetSw);
838 dest1IsEdge = config.isEdgeDevice(destSw1);
839 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
840 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
841 if (destSw2 != null) {
842 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
843 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
844 }
845 } catch (DeviceConfigNotFoundException e) {
846 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700847 return false;
848 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700849
850 if (targetIsEdge && dest1IsEdge) {
851 subnets = (subnets != null && !subnets.isEmpty())
852 ? Sets.newHashSet(subnets)
853 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Dasa4020382018-02-14 14:14:54 -0800854 // XXX - Rethink this - ignoring routerIPs in all other switches
855 // even edge to edge switches
Saurav Das7bcbe702017-06-13 15:35:54 -0700856 /*subnets.add(dest1RouterIpv4.toIpPrefix());
857 if (dest1RouterIpv6 != null) {
858 subnets.add(dest1RouterIpv6.toIpPrefix());
859 }
860 if (destSw2 != null && dest2RouterIpv4 != null) {
861 subnets.add(dest2RouterIpv4.toIpPrefix());
862 if (dest2RouterIpv6 != null) {
863 subnets.add(dest2RouterIpv6.toIpPrefix());
864 }
865 }*/
866 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
867 + "for subnets {}", targetSw, destSw1,
868 (destSw2 != null) ? ("& " + destSw2) : "",
869 subnets);
870 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
871 destSw1, destSw2,
872 nextHops);
873 if (!result) {
874 return false;
875 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700876 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700877
878 if (!targetIsEdge && dest1IsEdge) {
879 // MPLS rules in all non-edge target devices. These rules are for
880 // individual destinations, even if the dsts are part of edge-pairs.
881 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
882 + "all MPLS rules", targetSw, destSw1);
883 result = rulePopulator.populateMplsRule(targetSw, destSw1,
884 nextHops.get(destSw1),
885 dest1RouterIpv4);
886 if (!result) {
887 return false;
888 }
889 if (dest1RouterIpv6 != null) {
Saurav Dasa4020382018-02-14 14:14:54 -0800890 int v4sid = 0, v6sid = 0;
891 try {
892 v4sid = config.getIPv4SegmentId(destSw1);
893 v6sid = config.getIPv6SegmentId(destSw1);
894 } catch (DeviceConfigNotFoundException e) {
895 log.warn(e.getMessage());
896 }
897 if (v4sid != v6sid) {
898 result = rulePopulator.populateMplsRule(targetSw, destSw1,
899 nextHops.get(destSw1),
900 dest1RouterIpv6);
901 if (!result) {
902 return false;
903 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700904 }
905 }
906 }
907
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -0700908 if (!targetIsEdge && !dest1IsEdge) {
909 // MPLS rules for inter-connected spines
910 // can be merged with above if, left it here for clarity
911 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
912 + "all MPLS rules", targetSw, destSw1);
913
914 result = rulePopulator.populateMplsRule(targetSw, destSw1,
915 nextHops.get(destSw1),
916 dest1RouterIpv4);
917 if (!result) {
918 return false;
919 }
920
921 if (dest1RouterIpv6 != null) {
922 int v4sid = 0, v6sid = 0;
923 try {
924 v4sid = config.getIPv4SegmentId(destSw1);
925 v6sid = config.getIPv6SegmentId(destSw1);
926 } catch (DeviceConfigNotFoundException e) {
927 log.warn(e.getMessage());
928 }
929 if (v4sid != v6sid) {
930 result = rulePopulator.populateMplsRule(targetSw, destSw1,
931 nextHops.get(destSw1),
932 dest1RouterIpv6);
933 if (!result) {
934 return false;
935 }
936 }
937 }
938 }
939
Saurav Das7bcbe702017-06-13 15:35:54 -0700940 // To save on ECMP groups
941 // avoid MPLS rules in non-edge-devices to non-edge-devices
942 // avoid MPLS transit rules in edge-devices
943 // avoid loopback IP rules in edge-devices to non-edge-devices
944 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700945 }
946
947 /**
948 * Processes a set a route-path changes by editing hash groups.
949 *
950 * @param routeChanges a set of route-path changes, where each route-path is
951 * a list with its first element the src-switch of the path
952 * and the second element the dst-switch of the path.
953 * @param linkOrSwitchFailed true if the route changes are for a failed
954 * switch or linkDown event
955 * @param failedSwitch the switchId if the route changes are for a failed switch,
956 * otherwise null
957 */
958 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
959 boolean linkOrSwitchFailed,
960 DeviceId failedSwitch) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700961 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
962 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700963 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700964 if (route.size() == 1) {
965 // route-path changes are from everyone else to this switch
966 DeviceId dstSw = route.get(0);
967 srManager.deviceService.getAvailableDevices().forEach(sw -> {
968 if (!sw.id().equals(dstSw)) {
969 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
970 }
971 });
972 } else {
973 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700974 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700975 }
Saurav Dasc568c342018-01-25 09:49:01 -0800976 boolean someFailed = false;
977 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Das9df5b7c2017-08-14 16:44:43 -0700978 for (ArrayList<DeviceId> route : changedRoutes) {
979 DeviceId targetSw = route.get(0);
980 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700981 if (linkOrSwitchFailed) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700982 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700983 // it's possible that we cannot fix hash groups for a route
984 // if the target switch has failed. Nevertheless the ecmp graph
985 // for the impacted switch must still be updated.
Saurav Das9df5b7c2017-08-14 16:44:43 -0700986 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700987 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
988 currentEcmpSpgMap.remove(targetSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700989 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -0700990 + "target:{}", dstSw, targetSw);
Saurav Dasc568c342018-01-25 09:49:01 -0800991 updatedDevices.add(targetSw);
992 updatedDevices.add(dstSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700993 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -0700994 }
995 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -0700996 if (success) {
997 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das9df5b7c2017-08-14 16:44:43 -0700998 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Dasc568c342018-01-25 09:49:01 -0800999 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1000 + " or switchdown", dstSw, targetSw);
1001 updatedDevices.add(targetSw);
1002 updatedDevices.add(dstSw);
1003 } else {
1004 someFailed = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -07001005 }
1006 } else {
1007 //linkup of seen before link
1008 boolean success = fixHashGroupsForRoute(route, false);
1009 if (success) {
1010 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1011 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1012 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -07001013 targetSw, dstSw);
Saurav Dasc568c342018-01-25 09:49:01 -08001014 updatedDevices.add(targetSw);
1015 updatedDevices.add(dstSw);
1016 } else {
1017 someFailed = true;
Saurav Dasc88d4662017-05-15 15:34:25 -07001018 }
1019 }
1020 }
Saurav Dasc568c342018-01-25 09:49:01 -08001021 if (!someFailed) {
1022 // here is where we update all devices not touched by this instance
1023 updatedEcmpSpgMap.keySet().stream()
1024 .filter(devId -> !updatedDevices.contains(devId))
1025 .forEach(devId -> {
1026 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1027 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1028 });
1029 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001030 }
1031
1032 /**
1033 * Edits hash groups in the src-switch (targetSw) of a route-path by
1034 * calling the groupHandler to either add or remove buckets in an existing
1035 * hash group.
1036 *
1037 * @param route a single list representing a route-path where the first element
1038 * is the src-switch (targetSw) of the route-path and the
1039 * second element is the dst-switch
1040 * @param revoke true if buckets in the hash-groups need to be removed;
1041 * false if buckets in the hash-groups need to be added
1042 * @return true if the hash group editing is successful
1043 */
1044 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1045 boolean revoke) {
1046 DeviceId targetSw = route.get(0);
1047 if (route.size() < 2) {
1048 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1049 return false;
1050 }
1051 DeviceId destSw = route.get(1);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001052 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001053 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001054 // figure out the new next hops at the targetSw towards the destSw
Saurav Das9df5b7c2017-08-14 16:44:43 -07001055 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001056 // call group handler to change hash group at targetSw
1057 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1058 if (grpHandler == null) {
1059 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1060 + " {} hash group buckets for route:{} ", targetSw,
1061 (revoke) ? "revoke" : "repopulate", route);
1062 return false;
1063 }
Saurav Das137f27f2018-06-11 17:02:31 -07001064 log.debug("{} hash-groups buckets For Route {} -> {} to new next-hops {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001065 (revoke) ? "revoke" : "repopulating",
1066 targetSw, destSw, nextHops);
1067 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1068 destSw, true)
1069 : grpHandler.fixHashGroups(targetSw, nextHops,
1070 destSw, false);
1071 }
1072
1073 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001074 * Start the flow rule population process if it was never started. The
1075 * process finishes successfully when all flow rules are set and stops with
1076 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -07001077 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001078 public void startPopulationProcess() {
1079 statusLock.lock();
1080 try {
1081 if (populationStatus == Status.IDLE
1082 || populationStatus == Status.SUCCEEDED
1083 || populationStatus == Status.ABORTED) {
1084 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -07001085 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -07001086 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1087 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001088 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001089 } finally {
1090 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001091 }
sangho20eff1d2015-04-13 15:15:58 -07001092 }
1093
Saurav Dasb5c236e2016-06-07 10:08:06 -07001094 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001095 * Revoke rules of given subnet in all edge switches.
1096 *
1097 * @param subnets subnet being removed
1098 * @return true if succeed
1099 */
1100 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1101 statusLock.lock();
1102 try {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001103 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1104 .map(Device::id)
1105 .filter(this::shouldProgram)
1106 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das7bcbe702017-06-13 15:35:54 -07001107 } finally {
1108 statusLock.unlock();
1109 }
1110 }
1111
1112 /**
Charles Chan2fde6d42017-08-23 14:46:43 -07001113 * Populates IP rules for a route that has direct connection to the switch
1114 * if the current instance is the master of the switch.
1115 *
1116 * @param deviceId device ID of the device that next hop attaches to
1117 * @param prefix IP prefix of the route
1118 * @param hostMac MAC address of the next hop
1119 * @param hostVlanId Vlan ID of the nexthop
1120 * @param outPort port where the next hop attaches to
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001121 * @param directHost host is of type direct or indirect
Charles Chan2fde6d42017-08-23 14:46:43 -07001122 */
1123 void populateRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001124 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001125 if (shouldProgram(deviceId)) {
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001126 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan2fde6d42017-08-23 14:46:43 -07001127 }
1128 }
1129
1130 /**
1131 * Removes IP rules for a route when the next hop is gone.
1132 * if the current instance is the master of the switch.
1133 *
1134 * @param deviceId device ID of the device that next hop attaches to
1135 * @param prefix IP prefix of the route
1136 * @param hostMac MAC address of the next hop
1137 * @param hostVlanId Vlan ID of the nexthop
1138 * @param outPort port that next hop attaches to
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001139 * @param directHost host is of type direct or indirect
Charles Chan2fde6d42017-08-23 14:46:43 -07001140 */
1141 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001142 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001143 if (shouldProgram(deviceId)) {
Ruchi Sahotaef0761c2019-01-28 01:08:18 +00001144 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
Charles Chan2fde6d42017-08-23 14:46:43 -07001145 }
1146 }
1147
Charles Chan2ff1bac2018-03-29 16:03:41 -07001148 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1149 if (shouldProgram(deviceId)) {
1150 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1151 }
1152 }
1153
1154 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1155 if (shouldProgram(deviceId)) {
1156 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1157 }
1158 }
1159
1160 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1161 VlanId vlanId, boolean popVlan, boolean install) {
1162 if (shouldProgram(deviceId)) {
1163 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1164 }
1165 }
1166
1167 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1168 VlanId vlanId, boolean popVlan, boolean install) {
1169 if (shouldProgram(deviceId)) {
1170 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1171 vlanId, popVlan, install);
1172 }
1173 }
1174
Charles Chan2fde6d42017-08-23 14:46:43 -07001175 /**
Jonghwan Hyun800d9d02018-04-09 09:40:50 -07001176 * Populates IP rules for a route when the next hop is double-tagged.
1177 *
1178 * @param deviceId device ID that next hop attaches to
1179 * @param prefix IP prefix of the route
1180 * @param hostMac MAC address of the next hop
1181 * @param innerVlan Inner Vlan ID of the next hop
1182 * @param outerVlan Outer Vlan ID of the next hop
1183 * @param outerTpid Outer TPID of the next hop
1184 * @param outPort port that the next hop attaches to
1185 */
1186 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1187 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1188 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1189 VlanId dummyVlan = srManager.allocateDummyVlanId(
1190 new ConnectPoint(deviceId, outPort), prefix.address());
1191 if (!dummyVlan.equals(VlanId.NONE)) {
1192 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1193 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1194 srManager.routingRulePopulator.processDoubleTaggedFilter(
1195 deviceId, outPort, outerVlan, innerVlan, true);
1196 } else {
1197 log.error("Failed to allocate dummy VLAN ID for host {} at {}/{}",
1198 prefix.address(), deviceId, outPort);
1199 }
1200 }
1201 }
1202
1203 /**
1204 * Revokes IP rules for a route when the next hop is double-tagged.
1205 *
1206 * @param deviceId device ID that next hop attaches to
1207 * @param prefix IP prefix of the route
1208 * @param hostMac MAC address of the next hop
1209 * @param innerVlan Inner Vlan ID of the next hop
1210 * @param outerVlan Outer Vlan ID of the next hop
1211 * @param outerTpid Outer TPID of the next hop
1212 * @param outPort port that the next hop attaches to
1213 */
1214 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1215 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1216 // Revoke route either if this node have the mastership (when device is available) or
1217 // if this node is the leader (even when device is unavailable)
1218 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1219 if (srManager.deviceService.isAvailable(deviceId)) {
1220 // Master node will revoke specified rule.
1221 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1222 return;
1223 }
1224
1225 // isLocalMaster will return false when the device is unavailable.
1226 // Verify if this node is the leader in that case.
1227 NodeId leader = srManager.leadershipService.runForLeadership(
1228 deviceId.toString()).leaderNodeId();
1229 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1230 // Leader node will revoke specified rule.
1231 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1232 return;
1233 }
1234 }
1235
1236 VlanId dummyVlan = srManager.dummyVlanIdStore().get(new DummyVlanIdStoreKey(
1237 new ConnectPoint(deviceId, outPort), prefix.address()));
1238 if (dummyVlan == null) {
1239 log.error("Failed to get dummyVlanId for host {} at {}/{}.",
1240 prefix.address(), deviceId, outPort);
1241 } else {
1242 srManager.routingRulePopulator.revokeDoubleTaggedRoute(
1243 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1244 srManager.routingRulePopulator.processDoubleTaggedFilter(
1245 deviceId, outPort, outerVlan, innerVlan, false);
1246 }
1247 }
1248
1249
1250 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001251 * Remove ECMP graph entry for the given device. Typically called when
1252 * device is no longer available.
1253 *
1254 * @param deviceId the device for which graphs need to be purged
1255 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001256 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Dasc568c342018-01-25 09:49:01 -08001257 statusLock.lock();
1258 try {
Saurav Dasc568c342018-01-25 09:49:01 -08001259 if (populationStatus == Status.STARTED) {
1260 log.warn("Previous rule population is not finished. Cannot"
1261 + " proceeed with purgeEcmpGraph for {}", deviceId);
1262 return;
1263 }
1264 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1265 currentEcmpSpgMap.remove(deviceId);
1266 if (updatedEcmpSpgMap != null) {
1267 updatedEcmpSpgMap.remove(deviceId);
1268 }
1269 } finally {
1270 statusLock.unlock();
Saurav Das7bcbe702017-06-13 15:35:54 -07001271 }
1272 }
1273
Saurav Das201762d2018-04-21 17:19:48 -07001274 /**
1275 * Attempts a full reroute of route-paths if topology has changed relatively
1276 * close to a mastership change event. Does not do a reroute if mastership
1277 * change is due to reasons other than a ONOS cluster event - for example a
1278 * call to balance-masters, or a switch up/down event.
1279 *
1280 * @param devId the device identifier for which mastership has changed
1281 * @param me the mastership event
1282 */
1283 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1284 // give small delay to absorb mastership events that are caused by
1285 // device that has disconnected from cluster
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001286 executorServiceMstChg.schedule(new MasterChange(devId, me),
1287 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das201762d2018-04-21 17:19:48 -07001288 }
1289
1290 protected final class MasterChange implements Runnable {
1291 private DeviceId devId;
1292 private MastershipEvent me;
1293 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1294 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
Saurav Das9a554292018-04-27 18:42:30 -07001295 private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
Saurav Das137f27f2018-06-11 17:02:31 -07001296 private static final long FULL_REROUTE_THRESHOLD = 10000; // ms
Saurav Das201762d2018-04-21 17:19:48 -07001297
1298 MasterChange(DeviceId devId, MastershipEvent me) {
1299 this.devId = devId;
1300 this.me = me;
1301 }
1302
1303 @Override
1304 public void run() {
1305 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1306 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1307
1308 // ignore event for lost switch if cluster event hasn't happened -
1309 // device down event will handle it
1310 if ((me.roleInfo().master() == null
1311 || !srManager.deviceService.isAvailable(devId))
1312 && !clusterEvent) {
1313 log.debug("Full reroute not required for lost device: {}/{} "
1314 + "clusterEvent/timeSince: {}/{}",
1315 devId, me.roleInfo(), clusterEvent, lce);
1316 return;
1317 }
1318
1319 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1320 long lde = Instant.now().toEpochMilli() - update;
1321 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1322
1323 // ignore event for recently connected switch if cluster event hasn't
1324 // happened - link up events will handle it
1325 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1326 && !clusterEvent) {
1327 log.debug("Full reroute not required for recently available"
1328 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1329 + "clusterEvent/timeSince: {}/{}",
1330 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1331 return;
1332 }
1333
Saurav Das9a554292018-04-27 18:42:30 -07001334 long lepe = Instant.now().toEpochMilli()
1335 - srManager.lastEdgePortEvent.toEpochMilli();
1336 boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
1337
Saurav Das201762d2018-04-21 17:19:48 -07001338 // if it gets here, then mastership change is likely due to onos
1339 // instance failure, or network partition in onos cluster
1340 // normally a mastership change like this does not require re-programming
1341 // but if topology changes happen at the same time then we may miss events
1342 if (!isRoutingStable() && clusterEvent) {
Saurav Das9a554292018-04-27 18:42:30 -07001343 log.warn("Mastership changed for dev: {}/{} while programming route-paths "
Saurav Das201762d2018-04-21 17:19:48 -07001344 + "due to clusterEvent {} ms ago .. attempting full reroute",
1345 devId, me.roleInfo(), lce);
1346 if (srManager.mastershipService.isLocalMaster(devId)) {
1347 // old master could have died when populating filters
1348 populatePortAddressingRules(devId);
1349 }
Saurav Das137f27f2018-06-11 17:02:31 -07001350 // old master could have died when creating groups
Saurav Das201762d2018-04-21 17:19:48 -07001351 // XXX right now we have no fine-grained way to only make changes
Saurav Das137f27f2018-06-11 17:02:31 -07001352 // for the route paths affected by this device. Thus we do a
1353 // full reroute after purging all hash groups. We also try to do
1354 // it only once, irrespective of the number of devices
1355 // that changed mastership when their master instance died.
1356 long lfrr = Instant.now().toEpochMilli() - lastFullReroute.toEpochMilli();
1357 boolean doFullReroute = lfrr > FULL_REROUTE_THRESHOLD;
1358 if (doFullReroute) {
1359 lastFullReroute = Instant.now();
1360 for (Device dev : srManager.deviceService.getDevices()) {
1361 if (shouldProgram(dev.id())) {
1362 srManager.purgeHashedNextObjectiveStore(dev.id());
1363 }
1364 }
1365 // give small delay to ensure entire store is purged
1366 executorServiceFRR.schedule(new FullRerouteAfterPurge(),
1367 PURGE_DELAY,
1368 TimeUnit.MILLISECONDS);
1369 } else {
1370 log.warn("Full reroute attempted {} ms ago .. skipping", lfrr);
1371 }
Saurav Das9a554292018-04-27 18:42:30 -07001372
1373 } else if (edgePortEvent && clusterEvent) {
1374 log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
1375 + "while edge-port event happened {} ms ago "
1376 + " .. reprogramming all edge-ports",
1377 devId, me.roleInfo(), lce, lepe);
1378 if (shouldProgram(devId)) {
1379 srManager.deviceService.getPorts(devId).stream()
1380 .filter(p -> srManager.interfaceService
1381 .isConfigured(new ConnectPoint(devId, p.number())))
1382 .forEach(p -> srManager.processPortUpdated(devId, p));
1383 }
1384
Saurav Das201762d2018-04-21 17:19:48 -07001385 } else {
1386 log.debug("Stable route-paths .. full reroute not attempted for "
1387 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1388 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1389 deviceEvent, lde, clusterEvent, lce);
1390 }
1391 }
1392 }
1393
Saurav Das137f27f2018-06-11 17:02:31 -07001394 /**
1395 * Performs a full reroute of routing rules in all the switches. Assumes
1396 * caller has purged hash groups from the nextObjective store, otherwise
1397 * re-uses ones available in the store.
1398 */
1399 protected final class FullRerouteAfterPurge implements Runnable {
1400 @Override
1401 public void run() {
1402 populateAllRoutingRules();
1403 }
1404 }
1405
1406
Saurav Das7bcbe702017-06-13 15:35:54 -07001407 //////////////////////////////////////
1408 // Routing helper methods and classes
1409 //////////////////////////////////////
1410
1411 /**
Saurav Das137f27f2018-06-11 17:02:31 -07001412 * Computes set of affected routes due to failed link. Assumes previous ecmp
1413 * shortest-path graph exists for a switch in order to compute affected
1414 * routes. If such a graph does not exist, the method returns null.
Saurav Dasb5c236e2016-06-07 10:08:06 -07001415 *
1416 * @param linkFail the failed link
1417 * @return the set of affected routes which may be empty if no routes were
Saurav Das137f27f2018-06-11 17:02:31 -07001418 * affected
Saurav Dasb5c236e2016-06-07 10:08:06 -07001419 */
sangho20eff1d2015-04-13 15:15:58 -07001420 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001421 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1422
1423 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001424 log.debug("Computing the impacted routes for device {} due to link fail",
1425 sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001426 if (!shouldProgram(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001427 lastProgrammed.remove(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001428 continue;
1429 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001430 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001431 // check for mastership change since last run
1432 if (!lastProgrammed.contains(sw.id())) {
Saurav Das137f27f2018-06-11 17:02:31 -07001433 log.warn("New responsibility for this node to program dev:{}"
Saurav Das201762d2018-04-21 17:19:48 -07001434 + " ... nuking current ECMPspg", sw.id());
1435 currentEcmpSpgMap.remove(sw.id());
1436 }
Saurav Das137f27f2018-06-11 17:02:31 -07001437 lastProgrammed.add(sw.id());
1438
Saurav Das9df5b7c2017-08-14 16:44:43 -07001439 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1440 if (ecmpSpg == null) {
Saurav Das137f27f2018-06-11 17:02:31 -07001441 log.warn("No existing ECMP graph for switch {}. Assuming "
1442 + "all route-paths have changed towards it.", rootSw);
1443 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
1444 if (targetSw.equals(rootSw)) {
1445 continue;
1446 }
1447 routes.add(Lists.newArrayList(targetSw, rootSw));
1448 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1449 }
1450 continue;
Saurav Das9df5b7c2017-08-14 16:44:43 -07001451 }
Saurav Das137f27f2018-06-11 17:02:31 -07001452
Saurav Das9df5b7c2017-08-14 16:44:43 -07001453 if (log.isDebugEnabled()) {
1454 log.debug("Root switch: {}", rootSw);
1455 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1456 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1457 }
1458 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1459 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1460 // figure out if the broken link affected any route-paths in this graph
1461 for (Integer itrIdx : switchVia.keySet()) {
1462 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1463 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1464 switchVia.get(itrIdx);
1465 for (DeviceId targetSw : swViaMap.keySet()) {
1466 log.trace("TargetSwitch {} --> RootSwitch {}",
1467 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001468 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1469 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001470 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001471 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001472 Set<ArrayList<DeviceId>> subLinks =
1473 computeLinks(targetSw, rootSw, swViaMap);
1474 for (ArrayList<DeviceId> alink: subLinks) {
1475 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1476 alink.get(1).equals(linkFail.dst().deviceId()))
1477 ||
1478 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1479 alink.get(1).equals(linkFail.src().deviceId()))) {
1480 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1481 ArrayList<DeviceId> aRoute = new ArrayList<>();
1482 aRoute.add(targetSw); // switch with rules to populate
1483 aRoute.add(rootSw); // towards this destination
1484 routes.add(aRoute);
1485 break;
1486 }
sangho20eff1d2015-04-13 15:15:58 -07001487 }
1488 }
1489 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001490
sangho20eff1d2015-04-13 15:15:58 -07001491 }
sangho45b009c2015-05-07 13:30:57 -07001492
sangho20eff1d2015-04-13 15:15:58 -07001493 }
sangho20eff1d2015-04-13 15:15:58 -07001494 return routes;
1495 }
1496
Saurav Das4e3224f2016-11-29 14:27:25 -08001497 /**
1498 * Computes set of affected routes due to new links or failed switches.
1499 *
Saurav Das604ab3a2018-03-18 21:28:15 -07001500 * @param failedSwitch deviceId of failed switch if any
Saurav Das4e3224f2016-11-29 14:27:25 -08001501 * @return the set of affected routes which may be empty if no routes were
1502 * affected
1503 */
Saurav Dase0d4c872018-03-05 14:37:16 -08001504 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001505 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001506 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001507
1508 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001509 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001510 if (!shouldProgram(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001511 lastProgrammed.remove(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001512 continue;
1513 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001514 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001515 if (log.isTraceEnabled()) {
1516 log.trace("Device links for dev: {}", rootSw);
1517 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1518 log.trace("{} -> {} ", link.src().deviceId(),
1519 link.dst().deviceId());
1520 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001521 }
Saurav Das201762d2018-04-21 17:19:48 -07001522 // check for mastership change since last run
1523 if (!lastProgrammed.contains(sw.id())) {
Saurav Das137f27f2018-06-11 17:02:31 -07001524 log.warn("New responsibility for this node to program dev:{}"
Saurav Das201762d2018-04-21 17:19:48 -07001525 + " ... nuking current ECMPspg", sw.id());
1526 currentEcmpSpgMap.remove(sw.id());
1527 }
Saurav Das137f27f2018-06-11 17:02:31 -07001528 lastProgrammed.add(sw.id());
Saurav Das7bcbe702017-06-13 15:35:54 -07001529 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1530 if (currEcmpSpg == null) {
1531 log.debug("No existing ECMP graph for device {}.. adding self as "
1532 + "changed route", rootSw);
1533 changedRtBldr.add(Lists.newArrayList(rootSw));
1534 continue;
1535 }
1536 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Das5a356042018-04-06 20:16:01 -07001537 if (newEcmpSpg == null) {
1538 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1539 continue;
1540 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001541 if (log.isDebugEnabled()) {
1542 log.debug("Root switch: {}", rootSw);
1543 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1544 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1545 }
1546 // first use the updated/new map to compare to current/existing map
1547 // as new links may have come up
1548 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1549 // then use the current/existing map to compare to updated/new map
1550 // as switch may have been removed
1551 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001552 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001553 }
sangho20eff1d2015-04-13 15:15:58 -07001554
Saurav Dase0d4c872018-03-05 14:37:16 -08001555 // handle clearing state for a failed switch in case the switch does
1556 // not have a pair, or the pair is not available
1557 if (failedSwitch != null) {
Charles Chanba6c5752018-04-02 11:46:38 -07001558 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1559 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dase0d4c872018-03-05 14:37:16 -08001560 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1561 srManager.deviceService.getDevices().forEach(dev -> {
1562 if (!dev.id().equals(failedSwitch) &&
1563 srManager.mastershipService.isLocalMaster(dev.id())) {
1564 log.debug(" : {}", dev.id());
1565 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1566 }
1567 });
1568 }
1569 }
1570
Saurav Das7bcbe702017-06-13 15:35:54 -07001571 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001572 for (ArrayList<DeviceId> route: changedRoutes) {
1573 log.debug("Route changes Target -> Root");
1574 if (route.size() == 1) {
1575 log.debug(" : all -> {}", route.get(0));
1576 } else {
1577 log.debug(" : {} -> {}", route.get(0), route.get(1));
1578 }
1579 }
1580 return changedRoutes;
1581 }
1582
1583 /**
1584 * For the root switch, searches all the target nodes reachable in the base
1585 * graph, and compares paths to the ones in the comp graph.
1586 *
1587 * @param base the graph that is indexed for all reachable target nodes
1588 * from the root node
1589 * @param comp the graph that the base graph is compared to
1590 * @param rootSw both ecmp graphs are calculated for the root node
1591 * @return all the routes that have changed in the base graph
1592 */
1593 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1594 EcmpShortestPathGraph comp,
1595 DeviceId rootSw) {
1596 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1597 ImmutableSet.builder();
1598 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1599 base.getAllLearnedSwitchesAndVia();
1600 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1601 comp.getAllLearnedSwitchesAndVia();
1602 for (Integer itrIdx : baseMap.keySet()) {
1603 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1604 baseMap.get(itrIdx);
1605 for (DeviceId targetSw : baseViaMap.keySet()) {
1606 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1607 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1608 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001609 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001610 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001611 route.add(targetSw); // switch with rules to populate
1612 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001613 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001614 }
1615 }
sangho45b009c2015-05-07 13:30:57 -07001616 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001617 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001618 }
1619
Saurav Das7bcbe702017-06-13 15:35:54 -07001620 /**
1621 * Returns the ECMP paths traversed to reach the target switch.
1622 *
1623 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1624 * @param targetSw the switch to reach from the root switch
1625 * @return the nodes traversed on ECMP paths to the target switch
1626 */
sangho20eff1d2015-04-13 15:15:58 -07001627 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001628 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001629 for (Integer itrIdx : switchVia.keySet()) {
1630 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1631 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001632 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001633 continue;
1634 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001635 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001636 }
1637 }
1638
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001639 return null;
sangho20eff1d2015-04-13 15:15:58 -07001640 }
1641
Saurav Das7bcbe702017-06-13 15:35:54 -07001642 /**
1643 * Utility method to break down a path from src to dst device into a collection
1644 * of links.
1645 *
1646 * @param src src device of the path
1647 * @param dst dst device of the path
1648 * @param viaMap path taken from src to dst device
1649 * @return collection of links in the path
1650 */
sangho20eff1d2015-04-13 15:15:58 -07001651 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1652 DeviceId dst,
1653 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1654 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1655 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1656 DeviceId linkSrc = src;
1657 DeviceId linkDst = dst;
1658 for (DeviceId viaDevice: via) {
1659 ArrayList<DeviceId> link = new ArrayList<>();
1660 linkDst = viaDevice;
1661 link.add(linkSrc);
1662 link.add(linkDst);
1663 subLinks.add(link);
1664 linkSrc = viaDevice;
1665 }
1666 ArrayList<DeviceId> link = new ArrayList<>();
1667 link.add(linkSrc);
1668 link.add(dst);
1669 subLinks.add(link);
1670 }
1671
1672 return subLinks;
1673 }
1674
Charles Chan93e71ba2016-04-29 14:38:22 -07001675 /**
Charles Chan2ff1bac2018-03-29 16:03:41 -07001676 * Determines whether this controller instance should program the
Saurav Das7bcbe702017-06-13 15:35:54 -07001677 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chan2ff1bac2018-03-29 16:03:41 -07001678 * <p>
1679 * Once an instance is elected, it will be the only instance responsible for programming
1680 * both devices in the pair until it goes down.
Charles Chan93e71ba2016-04-29 14:38:22 -07001681 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001682 * @param deviceId device identifier to consider for routing
Charles Chan2ff1bac2018-03-29 16:03:41 -07001683 * @return true if current instance should handle the routing for given device
Charles Chan93e71ba2016-04-29 14:38:22 -07001684 */
Charles Chan2ff1bac2018-03-29 16:03:41 -07001685 boolean shouldProgram(DeviceId deviceId) {
Charles Chan50bb6ef2018-04-18 18:41:05 -07001686 Boolean cached = shouldProgramCache.get(deviceId);
1687 if (cached != null) {
Saurav Das201762d2018-04-21 17:19:48 -07001688 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001689 return cached;
1690 }
1691
Charles Chan2ff1bac2018-03-29 16:03:41 -07001692 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001693
Charles Chan2ff1bac2018-03-29 16:03:41 -07001694 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1695 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1696 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
Saurav Das137f27f2018-06-11 17:02:31 -07001697 log.debug("Evaluate shouldProgram {}/pair={}. currentNodeId={}, master={}, pairMaster={}",
Charles Chan2ff1bac2018-03-29 16:03:41 -07001698 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1699
1700 // No pair device configured. Only handle when current instance is the master of the device
1701 if (!pairDeviceId.isPresent()) {
Saurav Das137f27f2018-06-11 17:02:31 -07001702 log.debug("No pair device. currentNodeId={}, master={}", currentNodeId, masterNodeId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001703 return currentNodeId.equals(masterNodeId);
sanghob35a6192015-04-01 13:05:26 -07001704 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001705
1706 // Should not handle if current instance is not the master of either switch
1707 if (!currentNodeId.equals(masterNodeId) &&
1708 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
Saurav Das137f27f2018-06-11 17:02:31 -07001709 log.debug("Current nodeId {} is neither the master of target device {} nor pair device {}",
Charles Chan2ff1bac2018-03-29 16:03:41 -07001710 currentNodeId, deviceId, pairDeviceId);
1711 return false;
1712 }
1713
1714 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1715
1716 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1717 if (v == null) {
1718 // There is no value in the map. Elect a node
1719 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1720 } else {
1721 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1722 // Use the node in the map if it is still alive and is a master of any of the two switches
1723 return v;
1724 } else {
1725 // Previously elected node is no longer the master of either switch. Re-elect a node.
1726 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1727 }
1728 }
1729 }));
1730
1731 if (king != null) {
Saurav Das137f27f2018-06-11 17:02:31 -07001732 log.debug("{} is king, should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001733 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chan2ff1bac2018-03-29 16:03:41 -07001734 return king.equals(currentNodeId);
1735 } else {
1736 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001737 shouldProgramCache.remove(deviceId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001738 return false;
1739 }
1740 }
1741
1742 /**
1743 * Elects a node who should take responsibility of programming devices.
1744 * @param nodeIds list of candidate node ID
1745 *
1746 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1747 */
1748 private NodeId elect(List<NodeId> nodeIds) {
1749 // Remove all null elements. This could happen when some device has no master
1750 nodeIds.removeAll(Collections.singleton(null));
1751 nodeIds.sort(null);
1752 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1753 }
1754
Charles Chan50bb6ef2018-04-18 18:41:05 -07001755 void invalidateShouldProgramCache(DeviceId deviceId) {
1756 shouldProgramCache.remove(deviceId);
1757 }
1758
Charles Chan2ff1bac2018-03-29 16:03:41 -07001759 /**
1760 * Returns a set of device ID, containing given device and its pair device if exist.
1761 *
1762 * @param deviceId Device ID
1763 * @return a set of device ID, containing given device and its pair device if exist.
1764 */
1765 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1766 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1767 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1768 return ret;
sanghob35a6192015-04-01 13:05:26 -07001769 }
1770
Charles Chan93e71ba2016-04-29 14:38:22 -07001771 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001772 * Returns the set of deviceIds which are the next hops from the targetSw
1773 * to the dstSw according to the latest ECMP spg.
1774 *
1775 * @param targetSw the switch for which the next-hops are desired
1776 * @param dstSw the switch to which the next-hops lead to from the targetSw
1777 * @return set of next hop deviceIds, could be empty if no next hops are found
1778 */
1779 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1780 boolean targetIsEdge = false;
1781 try {
1782 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1783 } catch (DeviceConfigNotFoundException e) {
1784 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1785 + "continuing to getNextHops", targetSw);
1786 }
1787
1788 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1789 if (ecmpSpg == null) {
1790 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1791 return ImmutableSet.of();
1792 }
1793 HashMap<Integer,
1794 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1795 ecmpSpg.getAllLearnedSwitchesAndVia();
1796 for (Integer itrIdx : switchVia.keySet()) {
1797 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1798 switchVia.get(itrIdx);
1799 for (DeviceId target : swViaMap.keySet()) {
1800 if (!target.equals(targetSw)) {
1801 continue;
1802 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001803 // optimization for spines to not use leaves to get
1804 // to a spine or other leaves. Also leaves should not use other
1805 // leaves to get to the destination
1806 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Dasa4020382018-02-14 14:14:54 -08001807 boolean pathdevIsEdge = false;
1808 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001809 log.debug("Evaluating next-hop in path: {}", via);
Saurav Dasa4020382018-02-14 14:14:54 -08001810 for (DeviceId pathdev : via) {
1811 try {
1812 pathdevIsEdge = srManager.deviceConfiguration
1813 .isEdgeDevice(pathdev);
1814 } catch (DeviceConfigNotFoundException e) {
1815 log.warn(e.getMessage());
1816 }
1817 if (pathdevIsEdge) {
Saurav Das137f27f2018-06-11 17:02:31 -07001818 log.debug("Avoiding {} hop path for targetSw:{}"
Saurav Dasa4020382018-02-14 14:14:54 -08001819 + " --> dstSw:{} which goes through an edge"
1820 + " device {} in path {}", itrIdx,
1821 targetSw, dstSw, pathdev, via);
1822 return ImmutableSet.of();
1823 }
1824 }
1825 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001826 }
1827 Set<DeviceId> nextHops = new HashSet<>();
1828 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1829 if (via.isEmpty()) {
1830 // the dstSw is the next-hop from the targetSw
1831 nextHops.add(dstSw);
1832 } else {
1833 // first elem is next-hop in each ECMP path
1834 nextHops.add(via.get(0));
1835 }
1836 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001837 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
1838 dstSw, nextHops);
Saurav Das7bcbe702017-06-13 15:35:54 -07001839 return nextHops;
1840 }
1841 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001842 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -07001843 return ImmutableSet.of(); //no next-hops found
1844 }
1845
Saurav Das7bcbe702017-06-13 15:35:54 -07001846 //////////////////////////////////////
1847 // Filtering rule creation
1848 //////////////////////////////////////
1849
1850 /**
Saurav Das018605f2017-02-18 14:05:44 -08001851 * Populates filtering rules for port, and punting rules
1852 * for gateway IPs, loopback IPs and arp/ndp traffic.
1853 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001854 *
1855 * @param deviceId Switch ID to set the rules
1856 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001857 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001858 // Although device is added, sometimes device store does not have the
1859 // ports for this device yet. It results in missing filtering rules in the
1860 // switch. We will attempt it a few times. If it still does not work,
1861 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001862 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001863 if (firstRun == null) {
1864 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001865 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001866 executorService.schedule(new RetryFilters(deviceId, firstRun),
1867 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001868 }
1869
1870 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001871 * RetryFilters populates filtering objectives for a device and keeps retrying
1872 * till the number of ports filtered are constant for a predefined number
1873 * of attempts.
1874 */
1875 protected final class RetryFilters implements Runnable {
1876 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1877 DeviceId devId;
1878 int counter;
1879 PortFilterInfo prevRun;
1880
1881 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001882 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001883 prevRun = previousRun;
1884 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001885 }
1886
1887 @Override
1888 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001889 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001890 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001891 boolean sameResult = prevRun.equals(thisRun);
1892 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1893 thisRun, sameResult);
Ray Milkeyc6c9b172018-02-26 09:36:31 -08001894 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001895 // exponentially increasing intervals for retries
1896 executorService.schedule(this,
1897 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1898 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001899 if (!sameResult) {
1900 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1901 }
Saurav Das59232cf2016-04-27 18:35:50 -07001902 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001903 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001904 }
Saurav Das59232cf2016-04-27 18:35:50 -07001905 }
sanghob35a6192015-04-01 13:05:26 -07001906}