blob: 0d25c108fb93bc5165eaf27894f05c7921ac787c [file] [log] [blame]
sangho80f11cb2015-04-01 13:05:26 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sangho80f11cb2015-04-01 13:05:26 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.segmentrouting;
17
Saurav Das62ae6792017-05-15 15:34:25 -070018import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableMap.Builder;
Charles Chanc22cef32016-04-29 14:38:22 -070020import com.google.common.collect.ImmutableSet;
Saurav Das1b391d52016-11-29 14:27:25 -080021import com.google.common.collect.Lists;
sanghofb7c7292015-04-13 15:15:58 -070022import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
Saurav Dasfbe74572017-08-03 18:30:35 -070024
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -070025import org.onlab.packet.EthType;
sangho9b169e32015-04-14 16:27:13 -070026import org.onlab.packet.Ip4Address;
Pier Ventreadb4ae62016-11-23 09:57:42 -080027import org.onlab.packet.Ip6Address;
sangho80f11cb2015-04-01 13:05:26 -070028import org.onlab.packet.IpPrefix;
Charles Chan910be6a2017-08-23 14:46:43 -070029import org.onlab.packet.MacAddress;
30import org.onlab.packet.VlanId;
Saurav Das261c3002017-06-13 15:35:54 -070031import org.onosproject.cluster.NodeId;
Saurav Das00e553b2018-04-21 17:19:48 -070032import org.onosproject.mastership.MastershipEvent;
Charles Chanc22cef32016-04-29 14:38:22 -070033import org.onosproject.net.ConnectPoint;
sangho80f11cb2015-04-01 13:05:26 -070034import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
sanghofb7c7292015-04-13 15:15:58 -070036import org.onosproject.net.Link;
Charles Chan910be6a2017-08-23 14:46:43 -070037import org.onosproject.net.PortNumber;
Charles Chan319d1a22015-11-03 10:42:14 -080038import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
39import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Das62ae6792017-05-15 15:34:25 -070040import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -070041import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
Charles Chand66d6712018-03-29 16:03:41 -070042import org.onosproject.store.serializers.KryoNamespaces;
43import org.onosproject.store.service.Serializer;
sangho80f11cb2015-04-01 13:05:26 -070044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -070047import java.time.Instant;
sangho80f11cb2015-04-01 13:05:26 -070048import java.util.ArrayList;
Charles Chand66d6712018-03-29 16:03:41 -070049import java.util.Collections;
sangho80f11cb2015-04-01 13:05:26 -070050import java.util.HashMap;
51import java.util.HashSet;
Saurav Das261c3002017-06-13 15:35:54 -070052import java.util.Iterator;
Charles Chand66d6712018-03-29 16:03:41 -070053import java.util.List;
Saurav Das261c3002017-06-13 15:35:54 -070054import java.util.Map;
Saurav Dasd1872b02016-12-02 15:43:47 -080055import java.util.Objects;
Charles Chan6dbcd252018-04-02 11:46:38 -070056import java.util.Optional;
sangho80f11cb2015-04-01 13:05:26 -070057import java.util.Set;
Saurav Das07c74602016-04-27 18:35:50 -070058import java.util.concurrent.ScheduledExecutorService;
59import java.util.concurrent.TimeUnit;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090060import java.util.concurrent.locks.Lock;
61import java.util.concurrent.locks.ReentrantLock;
Saurav Dasdc7f2752018-03-18 21:28:15 -070062import java.util.stream.Stream;
63
Pier Ventreadb4ae62016-11-23 09:57:42 -080064import static com.google.common.base.Preconditions.checkNotNull;
65import static java.util.concurrent.Executors.newScheduledThreadPool;
66import static org.onlab.util.Tools.groupedThreads;
sangho80f11cb2015-04-01 13:05:26 -070067
Charles Chanb7f75ac2016-01-11 18:28:54 -080068/**
69 * Default routing handler that is responsible for route computing and
70 * routing rule population.
71 */
sangho80f11cb2015-04-01 13:05:26 -070072public class DefaultRoutingHandler {
Saurav Dasf9332192017-02-18 14:05:44 -080073 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey092e9e22018-02-01 13:49:47 -080074 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Dasf9332192017-02-18 14:05:44 -080075 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasfbe74572017-08-03 18:30:35 -070076 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das00e553b2018-04-21 17:19:48 -070077 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Charles Chanc22cef32016-04-29 14:38:22 -070078 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sangho80f11cb2015-04-01 13:05:26 -070079
80 private SegmentRoutingManager srManager;
81 private RoutingRulePopulator rulePopulator;
Shashikanth VH0637b162015-12-11 01:32:44 +053082 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
83 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho9b169e32015-04-14 16:27:13 -070084 private DeviceConfiguration config;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +090085 private final Lock statusLock = new ReentrantLock();
86 private volatile Status populationStatus;
Yuta HIGUCHIebee2f12016-07-21 16:54:33 -070087 private ScheduledExecutorService executorService
Saurav Dasd1872b02016-12-02 15:43:47 -080088 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Das49368392018-04-23 18:42:12 -070089 private ScheduledExecutorService executorServiceMstChg
90 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
91
Saurav Das00e553b2018-04-21 17:19:48 -070092 private Instant lastRoutingChange = Instant.EPOCH;
sangho80f11cb2015-04-01 13:05:26 -070093
Saurav Das00e553b2018-04-21 17:19:48 -070094 // Distributed store to keep track of ONOS instance that should program the
95 // device pair. There should be only one instance (the king) that programs the same pair.
Charles Chand66d6712018-03-29 16:03:41 -070096 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chanfbcb8812018-04-18 18:41:05 -070097 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chand66d6712018-03-29 16:03:41 -070098
Saurav Das00e553b2018-04-21 17:19:48 -070099 // Local store to keep track of all devices that this instance was responsible
100 // for programming in the last run. Helps to determine if mastership changed
101 // during a run - only relevant for programming as a result of topo change.
102 Set<DeviceId> lastProgrammed;
103
sangho80f11cb2015-04-01 13:05:26 -0700104 /**
105 * Represents the default routing population status.
106 */
107 public enum Status {
108 // population process is not started yet.
109 IDLE,
110
111 // population process started.
112 STARTED,
113
Srikanth Vavilapalli64505482015-04-21 13:04:13 -0700114 // population process was aborted due to errors, mostly for groups not
115 // found.
sangho80f11cb2015-04-01 13:05:26 -0700116 ABORTED,
117
118 // population process was finished successfully.
119 SUCCEEDED
120 }
121
122 /**
123 * Creates a DefaultRoutingHandler object.
124 *
125 * @param srManager SegmentRoutingManager object
126 */
Charles Chand66d6712018-03-29 16:03:41 -0700127 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chanfbcb8812018-04-18 18:41:05 -0700128 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
129 .withName("sr-should-program")
130 .withSerializer(Serializer.using(KryoNamespaces.API))
131 .withRelaxedReadConsistency()
132 .build().asJavaMap();
133 this.shouldProgramCache = Maps.newConcurrentMap();
134 update(srManager);
135 }
136
137 /**
138 * Updates a DefaultRoutingHandler object.
139 *
140 * @param srManager SegmentRoutingManager object
141 */
142 void update(SegmentRoutingManager srManager) {
sangho80f11cb2015-04-01 13:05:26 -0700143 this.srManager = srManager;
144 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho9b169e32015-04-14 16:27:13 -0700145 this.config = checkNotNull(srManager.deviceConfiguration);
sangho80f11cb2015-04-01 13:05:26 -0700146 this.populationStatus = Status.IDLE;
sanghofb7c7292015-04-13 15:15:58 -0700147 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das00e553b2018-04-21 17:19:48 -0700148 this.lastProgrammed = Sets.newConcurrentHashSet();
sangho80f11cb2015-04-01 13:05:26 -0700149 }
150
151 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700152 * Returns an immutable copy of the current ECMP shortest-path graph as
153 * computed by this controller instance.
154 *
Saurav Das261c3002017-06-13 15:35:54 -0700155 * @return immutable copy of the current ECMP graph
Saurav Das62ae6792017-05-15 15:34:25 -0700156 */
157 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
158 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
159 currentEcmpSpgMap.entrySet().forEach(entry -> {
160 if (entry.getValue() != null) {
161 builder.put(entry.getKey(), entry.getValue());
162 }
163 });
164 return builder.build();
165 }
166
Saurav Dasfbe74572017-08-03 18:30:35 -0700167 /**
168 * Acquires the lock used when making routing changes.
169 */
170 public void acquireRoutingLock() {
171 statusLock.lock();
172 }
173
174 /**
175 * Releases the lock used when making routing changes.
176 */
177 public void releaseRoutingLock() {
178 statusLock.unlock();
179 }
180
181 /**
182 * Determines if routing in the network has been stable in the last
183 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
184 * routing change timestamp.
185 *
186 * @return true if stable
187 */
188 public boolean isRoutingStable() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700189 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
190 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700191 log.trace("Routing stable since {}s", now - last);
Saurav Dasfbe74572017-08-03 18:30:35 -0700192 return (now - last) > STABLITY_THRESHOLD;
193 }
194
Saurav Das49368392018-04-23 18:42:12 -0700195 /**
196 * Gracefully shuts down the defaultRoutingHandler. Typically called when
197 * the app is deactivated
198 */
199 public void shutdown() {
200 executorService.shutdown();
201 executorServiceMstChg.shutdown();
202 }
Saurav Dasfbe74572017-08-03 18:30:35 -0700203
Saurav Das261c3002017-06-13 15:35:54 -0700204 //////////////////////////////////////
205 // Route path handling
206 //////////////////////////////////////
207
Saurav Dase6c448a2018-01-18 12:07:33 -0800208 /* The following three methods represent the three major ways in which
209 * route-path handling is triggered in the network
Saurav Das261c3002017-06-13 15:35:54 -0700210 * a) due to configuration change
211 * b) due to route-added event
212 * c) due to change in the topology
213 */
214
Saurav Das62ae6792017-05-15 15:34:25 -0700215 /**
Saurav Das261c3002017-06-13 15:35:54 -0700216 * Populates all routing rules to all switches. Typically triggered at
217 * startup or after a configuration event.
sangho80f11cb2015-04-01 13:05:26 -0700218 */
Saurav Das62ae6792017-05-15 15:34:25 -0700219 public void populateAllRoutingRules() {
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700220 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900221 statusLock.lock();
222 try {
Saurav Das261c3002017-06-13 15:35:54 -0700223 if (populationStatus == Status.STARTED) {
224 log.warn("Previous rule population is not finished. Cannot"
225 + " proceed with populateAllRoutingRules");
226 return;
227 }
228
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900229 populationStatus = Status.STARTED;
230 rulePopulator.resetCounter();
Saurav Das261c3002017-06-13 15:35:54 -0700231 log.info("Starting to populate all routing rules");
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900232 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sangho80f11cb2015-04-01 13:05:26 -0700233
Saurav Das261c3002017-06-13 15:35:54 -0700234 // take a snapshot of the topology
235 updatedEcmpSpgMap = new HashMap<>();
236 Set<EdgePair> edgePairs = new HashSet<>();
237 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart61e24e12017-11-30 18:23:42 -0800238 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das261c3002017-06-13 15:35:54 -0700239 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart61e24e12017-11-30 18:23:42 -0800240 new EcmpShortestPathGraph(dstSw, srManager);
241 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700242 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
243 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700244 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700245 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
246 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
247 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700248 }
Charles Chand66d6712018-03-29 16:03:41 -0700249
250 if (!shouldProgram(dstSw)) {
Saurav Das00e553b2018-04-21 17:19:48 -0700251 lastProgrammed.remove(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900252 continue;
Saurav Das00e553b2018-04-21 17:19:48 -0700253 } else {
254 lastProgrammed.add(dstSw);
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900255 }
Saurav Das00e553b2018-04-21 17:19:48 -0700256 // To do a full reroute, assume all route-paths have changed
Charles Chand66d6712018-03-29 16:03:41 -0700257 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart61e24e12017-11-30 18:23:42 -0800258 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
259 if (targetSw.equals(dev)) {
Saurav Das261c3002017-06-13 15:35:54 -0700260 continue;
261 }
Jonathan Hart61e24e12017-11-30 18:23:42 -0800262 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das261c3002017-06-13 15:35:54 -0700263 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900264 }
Saurav Das261c3002017-06-13 15:35:54 -0700265 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900266
Saurav Das261c3002017-06-13 15:35:54 -0700267 if (!redoRouting(routeChanges, edgePairs, null)) {
268 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
269 populationStatus = Status.ABORTED;
270 log.warn("Failed to repopulate all routing rules.");
271 return;
sangho80f11cb2015-04-01 13:05:26 -0700272 }
273
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900274 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
275 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700276 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900277 rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700278 return;
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900279 } finally {
280 statusLock.unlock();
sangho80f11cb2015-04-01 13:05:26 -0700281 }
sangho80f11cb2015-04-01 13:05:26 -0700282 }
283
sanghofb7c7292015-04-13 15:15:58 -0700284 /**
Saurav Das261c3002017-06-13 15:35:54 -0700285 * Populate rules from all other edge devices to the connect-point(s)
286 * specified for the given subnets.
287 *
288 * @param cpts connect point(s) of the subnets being added
289 * @param subnets subnets being added
Charles Chan910be6a2017-08-23 14:46:43 -0700290 */
291 // XXX refactor
Saurav Das261c3002017-06-13 15:35:54 -0700292 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan6db55b92017-09-11 15:21:57 -0700293 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
294 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
295 return;
296 }
297
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700298 lastRoutingChange = Instant.now();
Saurav Das261c3002017-06-13 15:35:54 -0700299 statusLock.lock();
300 try {
301 if (populationStatus == Status.STARTED) {
302 log.warn("Previous rule population is not finished. Cannot"
303 + " proceed with routing rules for added routes");
304 return;
305 }
306 populationStatus = Status.STARTED;
307 rulePopulator.resetCounter();
Charles Chan910be6a2017-08-23 14:46:43 -0700308 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
309 subnets, cpts);
Saurav Das6430f412018-01-25 09:49:01 -0800310 // In principle an update to a subnet/prefix should not require a
311 // new ECMPspg calculation as it is not a topology event. As a
312 // result, we use the current/existing ECMPspg in the updated map
313 // used by the redoRouting method.
Saurav Das6de6ffd2018-02-09 09:15:03 -0800314 if (updatedEcmpSpgMap == null) {
315 updatedEcmpSpgMap = new HashMap<>();
316 }
Saurav Das6430f412018-01-25 09:49:01 -0800317 currentEcmpSpgMap.entrySet().forEach(entry -> {
318 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase321cff2018-02-09 17:26:45 -0800319 if (log.isTraceEnabled()) {
320 log.trace("Root switch: {}", entry.getKey());
321 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Das6430f412018-01-25 09:49:01 -0800322 }
323 });
Saurav Das261c3002017-06-13 15:35:54 -0700324 Set<EdgePair> edgePairs = new HashSet<>();
325 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
326 boolean handleRouting = false;
327
328 if (cpts.size() == 2) {
329 // ensure connect points are edge-pairs
330 Iterator<ConnectPoint> iter = cpts.iterator();
331 DeviceId dev1 = iter.next().deviceId();
Charles Chan6dbcd252018-04-02 11:46:38 -0700332 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
333 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
334 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700335 } else {
336 log.warn("Connectpoints {} for subnets {} not on "
337 + "pair-devices.. aborting populateSubnet", cpts, subnets);
338 populationStatus = Status.ABORTED;
339 return;
340 }
341 for (ConnectPoint cp : cpts) {
Saurav Das6430f412018-01-25 09:49:01 -0800342 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
343 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700344 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800345 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
346 log.warn("populateSubnet: no updated graph for dev:{}"
347 + " ... creating", cp.deviceId());
348 }
Charles Chand66d6712018-03-29 16:03:41 -0700349 if (!shouldProgram(cp.deviceId())) {
Saurav Das261c3002017-06-13 15:35:54 -0700350 continue;
351 }
352 handleRouting = true;
353 }
354 } else {
355 // single connect point
356 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Das6430f412018-01-25 09:49:01 -0800357 if (updatedEcmpSpgMap.get(dstSw) == null) {
358 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das261c3002017-06-13 15:35:54 -0700359 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Das6430f412018-01-25 09:49:01 -0800360 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
361 log.warn("populateSubnet: no updated graph for dev:{}"
362 + " ... creating", dstSw);
363 }
Charles Chand66d6712018-03-29 16:03:41 -0700364 handleRouting = shouldProgram(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700365 }
366
367 if (!handleRouting) {
368 log.debug("This instance is not handling ecmp routing to the "
369 + "connectPoint(s) {}", cpts);
370 populationStatus = Status.ABORTED;
371 return;
372 }
373
374 // if it gets here, this instance should handle routing for the
375 // connectpoint(s). Assume all route-paths have to be updated to
376 // the connectpoint(s) with the following exceptions
377 // 1. if target is non-edge no need for routing rules
378 // 2. if target is one of the connectpoints
379 for (ConnectPoint cp : cpts) {
380 DeviceId dstSw = cp.deviceId();
381 for (Device targetSw : srManager.deviceService.getDevices()) {
382 boolean isEdge = false;
383 try {
384 isEdge = config.isEdgeDevice(targetSw.id());
385 } catch (DeviceConfigNotFoundException e) {
Charles Chaneaf3c9b2018-02-16 17:20:54 -0800386 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
387 continue;
Saurav Das261c3002017-06-13 15:35:54 -0700388 }
Charles Chan6dbcd252018-04-02 11:46:38 -0700389 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das261c3002017-06-13 15:35:54 -0700390 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chan6dbcd252018-04-02 11:46:38 -0700391 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das261c3002017-06-13 15:35:54 -0700392 continue;
393 }
394 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
395 }
396 }
397
398 if (!redoRouting(routeChanges, edgePairs, subnets)) {
399 log.debug("populateSubnet: populationStatus is ABORTED");
400 populationStatus = Status.ABORTED;
401 log.warn("Failed to repopulate the rules for subnet.");
402 return;
403 }
404
405 log.debug("populateSubnet: populationStatus is SUCCEEDED");
406 populationStatus = Status.SUCCEEDED;
407 log.info("Completed subnet population. Total # of rules pushed : {}",
408 rulePopulator.getCounter());
409 return;
410
411 } finally {
412 statusLock.unlock();
413 }
414 }
415
416 /**
Saurav Das62ae6792017-05-15 15:34:25 -0700417 * Populates the routing rules or makes hash group changes according to the
418 * route-path changes due to link failure, switch failure or link up. This
419 * method should only be called for one of these three possible event-types.
Saurav Dasdc7f2752018-03-18 21:28:15 -0700420 * Note that when a switch goes away, all of its links fail as well, but
421 * this is handled as a single switch removal event.
sanghofb7c7292015-04-13 15:15:58 -0700422 *
Saurav Dasdc7f2752018-03-18 21:28:15 -0700423 * @param linkDown the single failed link, or null for other conditions such
424 * as link-up or a removed switch
Saurav Das62ae6792017-05-15 15:34:25 -0700425 * @param linkUp the single link up, or null for other conditions such as
Saurav Dasdc7f2752018-03-18 21:28:15 -0700426 * link-down or a removed switch
427 * @param switchDown the removed switch, or null for other conditions such
428 * as link-down or link-up
429 * @param seenBefore true if this event is for a linkUp or linkDown for a
430 * seen link
431 */
432 // TODO This method should be refactored into three separated methods
Charles Chan9d2dd552018-06-19 20:56:33 -0700433 public void populateRoutingRulesForLinkStatusChange(Link linkDown, Link linkUp,
434 DeviceId switchDown, boolean seenBefore) {
Saurav Dasdc7f2752018-03-18 21:28:15 -0700435 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
436 .count() != 1) {
Saurav Das62ae6792017-05-15 15:34:25 -0700437 log.warn("Only one event can be handled for link status change .. aborting");
438 return;
439 }
Saurav Dasdc7f2752018-03-18 21:28:15 -0700440
Yuta HIGUCHIc9d93472017-08-18 23:16:35 -0700441 lastRoutingChange = Instant.now();
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900442 statusLock.lock();
443 try {
sanghofb7c7292015-04-13 15:15:58 -0700444
445 if (populationStatus == Status.STARTED) {
Saurav Das261c3002017-06-13 15:35:54 -0700446 log.warn("Previous rule population is not finished. Cannot"
Saurav Das6430f412018-01-25 09:49:01 -0800447 + " proceeed with routingRules for Topology change");
Saurav Das62ae6792017-05-15 15:34:25 -0700448 return;
sanghofb7c7292015-04-13 15:15:58 -0700449 }
450
Saurav Das261c3002017-06-13 15:35:54 -0700451 // Take snapshots of the topology
sangho28d0b6d2015-05-07 13:30:57 -0700452 updatedEcmpSpgMap = new HashMap<>();
Saurav Das261c3002017-06-13 15:35:54 -0700453 Set<EdgePair> edgePairs = new HashSet<>();
sangho28d0b6d2015-05-07 13:30:57 -0700454 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH0637b162015-12-11 01:32:44 +0530455 EcmpShortestPathGraph ecmpSpgUpdated =
456 new EcmpShortestPathGraph(sw.id(), srManager);
sangho28d0b6d2015-05-07 13:30:57 -0700457 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chan6dbcd252018-04-02 11:46:38 -0700458 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
459 if (pairDev.isPresent()) {
Saurav Das261c3002017-06-13 15:35:54 -0700460 // pairDev may not be available yet, but we still need to add
Charles Chan6dbcd252018-04-02 11:46:38 -0700461 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
462 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
463 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das261c3002017-06-13 15:35:54 -0700464 }
sangho28d0b6d2015-05-07 13:30:57 -0700465 }
466
Saurav Das6430f412018-01-25 09:49:01 -0800467 log.info("Starting to populate routing rules from Topology change");
sanghodf0153f2015-05-05 14:13:34 -0700468
sanghofb7c7292015-04-13 15:15:58 -0700469 Set<ArrayList<DeviceId>> routeChanges;
Saurav Das62ae6792017-05-15 15:34:25 -0700470 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700471 + "populationStatus is STARTED");
sanghofb7c7292015-04-13 15:15:58 -0700472 populationStatus = Status.STARTED;
Saurav Das6430f412018-01-25 09:49:01 -0800473 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
474 boolean hashGroupsChanged = false;
Saurav Das1b391d52016-11-29 14:27:25 -0800475 // try optimized re-routing
Saurav Das62ae6792017-05-15 15:34:25 -0700476 if (linkDown == null) {
477 // either a linkUp or a switchDown - compute all route changes by
478 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dascea556f2018-03-05 14:37:16 -0800479 routeChanges = computeRouteChange(switchDown);
Saurav Das62ae6792017-05-15 15:34:25 -0700480
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700481 // deal with linkUp of a seen-before link
Saurav Dasdc7f2752018-03-18 21:28:15 -0700482 if (linkUp != null && seenBefore) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700483 // link previously seen before
484 // do hash-bucket changes instead of a re-route
485 processHashGroupChange(routeChanges, false, null);
486 // clear out routesChanges so a re-route is not attempted
487 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800488 hashGroupsChanged = true;
Saurav Das62ae6792017-05-15 15:34:25 -0700489 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700490 // for a linkUp of a never-seen-before link
491 // let it fall through to a reroute of the routeChanges
Saurav Das62ae6792017-05-15 15:34:25 -0700492
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700493 //deal with switchDown
494 if (switchDown != null) {
495 processHashGroupChange(routeChanges, true, switchDown);
496 // clear out routesChanges so a re-route is not attempted
497 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800498 hashGroupsChanged = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700499 }
sanghofb7c7292015-04-13 15:15:58 -0700500 } else {
Saurav Das62ae6792017-05-15 15:34:25 -0700501 // link has gone down
502 // Compare existing ECMP SPG only with the link that went down
503 routeChanges = computeDamagedRoutes(linkDown);
504 if (routeChanges != null) {
505 processHashGroupChange(routeChanges, true, null);
506 // clear out routesChanges so a re-route is not attempted
507 routeChanges = ImmutableSet.of();
Saurav Das6430f412018-01-25 09:49:01 -0800508 hashGroupsChanged = true;
Saurav Das62ae6792017-05-15 15:34:25 -0700509 }
sanghofb7c7292015-04-13 15:15:58 -0700510 }
511
Saurav Das1b391d52016-11-29 14:27:25 -0800512 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb149be12016-06-07 10:08:06 -0700513 if (routeChanges == null) {
Saurav Das6430f412018-01-25 09:49:01 -0800514 log.warn("Optimized routing failed... opting for full reroute");
Saurav Das261c3002017-06-13 15:35:54 -0700515 populationStatus = Status.ABORTED;
Saurav Das62ae6792017-05-15 15:34:25 -0700516 populateAllRoutingRules();
517 return;
Saurav Dasb149be12016-06-07 10:08:06 -0700518 }
519
sanghofb7c7292015-04-13 15:15:58 -0700520 if (routeChanges.isEmpty()) {
Saurav Das6430f412018-01-25 09:49:01 -0800521 if (hashGroupsChanged) {
522 log.info("Hash-groups changed for link status change");
523 } else {
524 log.info("No re-route or re-hash attempted for the link"
525 + " status change");
526 updatedEcmpSpgMap.keySet().forEach(devId -> {
527 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
528 log.debug("Updating ECMPspg for remaining dev:{}", devId);
529 });
530 }
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700531 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700532 populationStatus = Status.SUCCEEDED;
Saurav Das62ae6792017-05-15 15:34:25 -0700533 return;
sanghofb7c7292015-04-13 15:15:58 -0700534 }
535
Saurav Das62ae6792017-05-15 15:34:25 -0700536 // reroute of routeChanges
Saurav Das261c3002017-06-13 15:35:54 -0700537 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700538 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sanghofb7c7292015-04-13 15:15:58 -0700539 populationStatus = Status.SUCCEEDED;
Saurav Das261c3002017-06-13 15:35:54 -0700540 log.info("Completed repopulation of rules for link-status change."
541 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Das62ae6792017-05-15 15:34:25 -0700542 return;
sanghofb7c7292015-04-13 15:15:58 -0700543 } else {
Srikanth Vavilapalli7cd16712015-05-04 09:48:09 -0700544 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sanghofb7c7292015-04-13 15:15:58 -0700545 populationStatus = Status.ABORTED;
Saurav Das261c3002017-06-13 15:35:54 -0700546 log.warn("Failed to repopulate the rules for link status change.");
Saurav Das62ae6792017-05-15 15:34:25 -0700547 return;
sanghofb7c7292015-04-13 15:15:58 -0700548 }
HIGUCHI Yuta16d8fd52015-09-08 16:16:31 +0900549 } finally {
550 statusLock.unlock();
sanghofb7c7292015-04-13 15:15:58 -0700551 }
552 }
553
Saurav Das62ae6792017-05-15 15:34:25 -0700554 /**
Saurav Das261c3002017-06-13 15:35:54 -0700555 * Processes a set a route-path changes by reprogramming routing rules and
556 * creating new hash-groups or editing them if necessary. This method also
557 * determines the next-hops for the route-path from the src-switch (target)
558 * of the path towards the dst-switch of the path.
Saurav Das62ae6792017-05-15 15:34:25 -0700559 *
Saurav Das261c3002017-06-13 15:35:54 -0700560 * @param routeChanges a set of route-path changes, where each route-path is
561 * a list with its first element the src-switch (target)
562 * of the path, and the second element the dst-switch of
563 * the path.
564 * @param edgePairs a set of edge-switches that are paired by configuration
565 * @param subnets a set of prefixes that need to be populated in the routing
566 * table of the target switch in the route-path. Can be null,
567 * in which case all the prefixes belonging to the dst-switch
568 * will be populated in the target switch
569 * @return true if successful in repopulating all routes
Saurav Das62ae6792017-05-15 15:34:25 -0700570 */
Saurav Das261c3002017-06-13 15:35:54 -0700571 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
572 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
573 // first make every entry two-elements
574 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
575 for (ArrayList<DeviceId> route : routeChanges) {
576 if (route.size() == 1) {
577 DeviceId dstSw = route.get(0);
578 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
579 if (ec == null) {
580 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
581 return false;
582 }
583 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
584 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
585 changedRoutes.add(Lists.newArrayList(target, dstSw));
586 });
587 });
588 } else {
589 DeviceId targetSw = route.get(0);
590 DeviceId dstSw = route.get(1);
591 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
592 }
593 }
594
595 // now process changedRoutes according to edgePairs
596 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
597 return false; //abort routing and fail fast
598 }
599
600 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Das6430f412018-01-25 09:49:01 -0800601 Set<DeviceId> updatedDevices = Sets.newHashSet();
602 if (!redoRoutingIndividualDests(subnets, changedRoutes,
603 updatedDevices)) {
Saurav Das261c3002017-06-13 15:35:54 -0700604 return false; //abort routing and fail fast
605 }
606
Saurav Das261c3002017-06-13 15:35:54 -0700607 // update ecmpSPG for all edge-pairs
608 for (EdgePair ep : edgePairs) {
609 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
610 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
611 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
612 }
Saurav Das6430f412018-01-25 09:49:01 -0800613
614 // here is where we update all devices not touched by this instance
615 updatedEcmpSpgMap.keySet().stream()
616 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
617 .filter(devId -> !updatedDevices.contains(devId))
618 .forEach(devId -> {
619 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
620 log.debug("Updating ECMPspg for remaining dev:{}", devId);
621 });
Saurav Das261c3002017-06-13 15:35:54 -0700622 return true;
623 }
624
625 /**
626 * Programs targetSw in the changedRoutes for given prefixes reachable by
627 * an edgePair. If no prefixes are given, the method will use configured
628 * subnets/prefixes. If some configured subnets belong only to a specific
629 * destination in the edgePair, then the target switch will be programmed
630 * only to that destination.
631 *
632 * @param edgePairs set of edge-pairs for which target will be programmed
633 * @param subnets a set of prefixes that need to be populated in the routing
634 * table of the target switch in the changedRoutes. Can be null,
635 * in which case all the configured prefixes belonging to the
636 * paired switches will be populated in the target switch
637 * @param changedRoutes a set of route-path changes, where each route-path is
638 * a list with its first element the src-switch (target)
639 * of the path, and the second element the dst-switch of
640 * the path.
641 * @return true if successful
642 */
643 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
644 Set<IpPrefix> subnets,
645 Set<ArrayList<DeviceId>> changedRoutes) {
646 for (EdgePair ep : edgePairs) {
647 // temp store for a target's changedRoutes to this edge-pair
648 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
649 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
650 while (i.hasNext()) {
651 ArrayList<DeviceId> route = i.next();
652 DeviceId dstSw = route.get(1);
653 if (ep.includes(dstSw)) {
654 // routeChange for edge pair found
655 // sort by target iff target is edge and remove from changedRoutes
656 DeviceId targetSw = route.get(0);
657 try {
658 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
659 continue;
660 }
661 } catch (DeviceConfigNotFoundException e) {
662 log.warn(e.getMessage() + "aborting redoRouting");
663 return false;
664 }
665 // route is from another edge to this edge-pair
666 if (targetRoutes.containsKey(targetSw)) {
667 targetRoutes.get(targetSw).add(route);
668 } else {
669 Set<ArrayList<DeviceId>> temp = new HashSet<>();
670 temp.add(route);
671 targetRoutes.put(targetSw, temp);
672 }
673 i.remove();
674 }
675 }
676 // so now for this edgepair we have a per target set of routechanges
677 // process target->edgePair route
678 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
679 targetRoutes.entrySet()) {
680 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
681 entry.getKey(), ep);
682 DeviceId targetSw = entry.getKey();
683 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
684 entry.getValue().forEach(route -> {
685 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
686 log.debug("route: target {} -> dst {} found with next-hops {}",
687 route.get(0), route.get(1), nhops);
688 perDstNextHops.put(route.get(1), nhops);
689 });
690 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
691 : subnets;
692 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
693 : subnets;
694 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
695 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Das6430f412018-01-25 09:49:01 -0800696 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
697 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das261c3002017-06-13 15:35:54 -0700698 // handle routing to subnets common to edge-pair
Saurav Das6430f412018-01-25 09:49:01 -0800699 // only if the targetSw is not part of the edge-pair and there
700 // exists a next hop to at least one of the devices in the edge-pair
701 if (!ep.includes(targetSw)
702 && ((nhDev1 != null && !nhDev1.isEmpty())
703 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das261c3002017-06-13 15:35:54 -0700704 if (!populateEcmpRoutingRulePartial(
705 targetSw,
706 ep.dev1, ep.dev2,
707 perDstNextHops,
708 Sets.intersection(ipDev1, ipDev2))) {
709 return false; // abort everything and fail fast
710 }
711 }
Saurav Das6430f412018-01-25 09:49:01 -0800712 // handle routing to subnets that only belong to dev1 only if
713 // a next-hop exists from the target to dev1
Saurav Das261c3002017-06-13 15:35:54 -0700714 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Das6430f412018-01-25 09:49:01 -0800715 if (!onlyDev1Subnets.isEmpty()
716 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700717 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800718 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das261c3002017-06-13 15:35:54 -0700719 if (!populateEcmpRoutingRulePartial(
720 targetSw,
721 ep.dev1, null,
722 onlyDev1NextHops,
723 onlyDev1Subnets)) {
724 return false; // abort everything and fail fast
725 }
726 }
Saurav Das6430f412018-01-25 09:49:01 -0800727 // handle routing to subnets that only belong to dev2 only if
728 // a next-hop exists from the target to dev2
Saurav Das261c3002017-06-13 15:35:54 -0700729 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Das6430f412018-01-25 09:49:01 -0800730 if (!onlyDev2Subnets.isEmpty()
731 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das261c3002017-06-13 15:35:54 -0700732 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Das6430f412018-01-25 09:49:01 -0800733 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das261c3002017-06-13 15:35:54 -0700734 if (!populateEcmpRoutingRulePartial(
735 targetSw,
736 ep.dev2, null,
737 onlyDev2NextHops,
738 onlyDev2Subnets)) {
739 return false; // abort everything and fail fast
740 }
741 }
742 }
743 // if it gets here it has succeeded for all targets to this edge-pair
744 }
745 return true;
746 }
747
748 /**
749 * Programs targetSw in the changedRoutes for given prefixes reachable by
750 * a destination switch that is not part of an edge-pair.
751 * If no prefixes are given, the method will use configured subnets/prefixes.
752 *
753 * @param subnets a set of prefixes that need to be populated in the routing
754 * table of the target switch in the changedRoutes. Can be null,
755 * in which case all the configured prefixes belonging to the
756 * paired switches will be populated in the target switch
757 * @param changedRoutes a set of route-path changes, where each route-path is
758 * a list with its first element the src-switch (target)
759 * of the path, and the second element the dst-switch of
760 * the path.
761 * @return true if successful
762 */
763 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Das6430f412018-01-25 09:49:01 -0800764 Set<ArrayList<DeviceId>> changedRoutes,
765 Set<DeviceId> updatedDevices) {
Saurav Das261c3002017-06-13 15:35:54 -0700766 // aggregate route-path changes for each dst device
767 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
768 new HashMap<>();
769 for (ArrayList<DeviceId> route: changedRoutes) {
770 DeviceId dstSw = route.get(1);
771 ArrayList<ArrayList<DeviceId>> deviceRoutes =
772 routesBydevice.get(dstSw);
773 if (deviceRoutes == null) {
774 deviceRoutes = new ArrayList<>();
775 routesBydevice.put(dstSw, deviceRoutes);
776 }
777 deviceRoutes.add(route);
778 }
779 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
780 ArrayList<ArrayList<DeviceId>> deviceRoutes =
781 routesBydevice.get(impactedDstDevice);
782 for (ArrayList<DeviceId> route: deviceRoutes) {
783 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
784 route.get(0), route.get(1));
785 DeviceId targetSw = route.get(0);
786 DeviceId dstSw = route.get(1); // same as impactedDstDevice
787 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Das8e46aa72018-01-09 17:38:44 -0800788 if (nextHops.isEmpty()) {
789 log.warn("Could not find next hop from target:{} --> dst {} "
790 + "skipping this route", targetSw, dstSw);
791 continue;
792 }
Saurav Das261c3002017-06-13 15:35:54 -0700793 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
794 nhops.put(dstSw, nextHops);
795 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
796 (subnets == null) ? Sets.newHashSet() : subnets)) {
797 return false; // abort routing and fail fast
798 }
799 log.debug("Populating flow rules from target: {} to dst: {}"
800 + " is successful", targetSw, dstSw);
801 }
802 //Only if all the flows for all impacted routes to a
803 //specific target are pushed successfully, update the
804 //ECMP graph for that target. Or else the next event
805 //would not see any changes in the ECMP graphs.
806 //In another case, the target switch has gone away, so
807 //routes can't be installed. In that case, the current map
808 //is updated here, without any flows being pushed.
809 currentEcmpSpgMap.put(impactedDstDevice,
810 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Das6430f412018-01-25 09:49:01 -0800811 updatedDevices.add(impactedDstDevice);
Saurav Das261c3002017-06-13 15:35:54 -0700812 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
813 }
814 return true;
815 }
816
817 /**
818 * Populate ECMP rules for subnets from target to destination via nexthops.
819 *
820 * @param targetSw Device ID of target switch in which rules will be programmed
821 * @param destSw1 Device ID of final destination switch to which the rules will forward
822 * @param destSw2 Device ID of paired destination switch to which the rules will forward
823 * A null deviceId indicates packets should only be sent to destSw1
Saurav Das97241862018-02-14 14:14:54 -0800824 * @param nextHops Map of a set of next hops per destSw
Saurav Das261c3002017-06-13 15:35:54 -0700825 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
826 * @return true if it succeeds in populating rules
827 */ // refactor
828 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
829 DeviceId destSw1,
830 DeviceId destSw2,
831 Map<DeviceId, Set<DeviceId>> nextHops,
832 Set<IpPrefix> subnets) {
833 boolean result;
834 // If both target switch and dest switch are edge routers, then set IP
835 // rule for both subnet and router IP.
836 boolean targetIsEdge;
837 boolean dest1IsEdge;
838 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
839 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
840
841 try {
842 targetIsEdge = config.isEdgeDevice(targetSw);
843 dest1IsEdge = config.isEdgeDevice(destSw1);
844 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
845 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
846 if (destSw2 != null) {
847 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
848 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
849 }
850 } catch (DeviceConfigNotFoundException e) {
851 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Das62ae6792017-05-15 15:34:25 -0700852 return false;
853 }
Saurav Das261c3002017-06-13 15:35:54 -0700854
855 if (targetIsEdge && dest1IsEdge) {
856 subnets = (subnets != null && !subnets.isEmpty())
857 ? Sets.newHashSet(subnets)
858 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Das97241862018-02-14 14:14:54 -0800859 // XXX - Rethink this - ignoring routerIPs in all other switches
860 // even edge to edge switches
Saurav Das261c3002017-06-13 15:35:54 -0700861 /*subnets.add(dest1RouterIpv4.toIpPrefix());
862 if (dest1RouterIpv6 != null) {
863 subnets.add(dest1RouterIpv6.toIpPrefix());
864 }
865 if (destSw2 != null && dest2RouterIpv4 != null) {
866 subnets.add(dest2RouterIpv4.toIpPrefix());
867 if (dest2RouterIpv6 != null) {
868 subnets.add(dest2RouterIpv6.toIpPrefix());
869 }
870 }*/
871 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
872 + "for subnets {}", targetSw, destSw1,
873 (destSw2 != null) ? ("& " + destSw2) : "",
874 subnets);
875 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
876 destSw1, destSw2,
877 nextHops);
878 if (!result) {
879 return false;
880 }
Saurav Das62ae6792017-05-15 15:34:25 -0700881 }
Saurav Das261c3002017-06-13 15:35:54 -0700882
883 if (!targetIsEdge && dest1IsEdge) {
884 // MPLS rules in all non-edge target devices. These rules are for
885 // individual destinations, even if the dsts are part of edge-pairs.
886 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
887 + "all MPLS rules", targetSw, destSw1);
888 result = rulePopulator.populateMplsRule(targetSw, destSw1,
889 nextHops.get(destSw1),
890 dest1RouterIpv4);
891 if (!result) {
892 return false;
893 }
894 if (dest1RouterIpv6 != null) {
Saurav Das97241862018-02-14 14:14:54 -0800895 int v4sid = 0, v6sid = 0;
896 try {
897 v4sid = config.getIPv4SegmentId(destSw1);
898 v6sid = config.getIPv6SegmentId(destSw1);
899 } catch (DeviceConfigNotFoundException e) {
900 log.warn(e.getMessage());
901 }
902 if (v4sid != v6sid) {
903 result = rulePopulator.populateMplsRule(targetSw, destSw1,
904 nextHops.get(destSw1),
905 dest1RouterIpv6);
906 if (!result) {
907 return false;
908 }
Saurav Das261c3002017-06-13 15:35:54 -0700909 }
910 }
911 }
912
Andreas Pantelopoulosfc4bc2a2018-03-12 16:30:20 -0700913 if (!targetIsEdge && !dest1IsEdge) {
914 // MPLS rules for inter-connected spines
915 // can be merged with above if, left it here for clarity
916 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
917 + "all MPLS rules", targetSw, destSw1);
918
919 result = rulePopulator.populateMplsRule(targetSw, destSw1,
920 nextHops.get(destSw1),
921 dest1RouterIpv4);
922 if (!result) {
923 return false;
924 }
925
926 if (dest1RouterIpv6 != null) {
927 int v4sid = 0, v6sid = 0;
928 try {
929 v4sid = config.getIPv4SegmentId(destSw1);
930 v6sid = config.getIPv6SegmentId(destSw1);
931 } catch (DeviceConfigNotFoundException e) {
932 log.warn(e.getMessage());
933 }
934 if (v4sid != v6sid) {
935 result = rulePopulator.populateMplsRule(targetSw, destSw1,
936 nextHops.get(destSw1),
937 dest1RouterIpv6);
938 if (!result) {
939 return false;
940 }
941 }
942 }
943 }
944
Saurav Das261c3002017-06-13 15:35:54 -0700945 // To save on ECMP groups
946 // avoid MPLS rules in non-edge-devices to non-edge-devices
947 // avoid MPLS transit rules in edge-devices
948 // avoid loopback IP rules in edge-devices to non-edge-devices
949 return true;
Saurav Das62ae6792017-05-15 15:34:25 -0700950 }
951
952 /**
953 * Processes a set a route-path changes by editing hash groups.
954 *
955 * @param routeChanges a set of route-path changes, where each route-path is
956 * a list with its first element the src-switch of the path
957 * and the second element the dst-switch of the path.
958 * @param linkOrSwitchFailed true if the route changes are for a failed
959 * switch or linkDown event
960 * @param failedSwitch the switchId if the route changes are for a failed switch,
961 * otherwise null
962 */
963 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
964 boolean linkOrSwitchFailed,
965 DeviceId failedSwitch) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700966 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
967 // first, ensure each routeChanges entry has two elements
Saurav Das62ae6792017-05-15 15:34:25 -0700968 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700969 if (route.size() == 1) {
970 // route-path changes are from everyone else to this switch
971 DeviceId dstSw = route.get(0);
972 srManager.deviceService.getAvailableDevices().forEach(sw -> {
973 if (!sw.id().equals(dstSw)) {
974 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
975 }
976 });
977 } else {
978 changedRoutes.add(route);
Saurav Das62ae6792017-05-15 15:34:25 -0700979 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700980 }
Saurav Das6430f412018-01-25 09:49:01 -0800981 boolean someFailed = false;
982 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700983 for (ArrayList<DeviceId> route : changedRoutes) {
984 DeviceId targetSw = route.get(0);
985 DeviceId dstSw = route.get(1);
Saurav Das62ae6792017-05-15 15:34:25 -0700986 if (linkOrSwitchFailed) {
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700987 boolean success = fixHashGroupsForRoute(route, true);
Saurav Das62ae6792017-05-15 15:34:25 -0700988 // it's possible that we cannot fix hash groups for a route
989 // if the target switch has failed. Nevertheless the ecmp graph
990 // for the impacted switch must still be updated.
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700991 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Das62ae6792017-05-15 15:34:25 -0700992 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
993 currentEcmpSpgMap.remove(targetSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700994 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Das62ae6792017-05-15 15:34:25 -0700995 + "target:{}", dstSw, targetSw);
Saurav Das6430f412018-01-25 09:49:01 -0800996 updatedDevices.add(targetSw);
997 updatedDevices.add(dstSw);
Saurav Dasfe0b05e2017-08-14 16:44:43 -0700998 continue;
Saurav Das62ae6792017-05-15 15:34:25 -0700999 }
1000 //linkfailed - update both sides
Saurav Das62ae6792017-05-15 15:34:25 -07001001 if (success) {
1002 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001003 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Das6430f412018-01-25 09:49:01 -08001004 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1005 + " or switchdown", dstSw, targetSw);
1006 updatedDevices.add(targetSw);
1007 updatedDevices.add(dstSw);
1008 } else {
1009 someFailed = true;
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001010 }
1011 } else {
1012 //linkup of seen before link
1013 boolean success = fixHashGroupsForRoute(route, false);
1014 if (success) {
1015 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1016 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1017 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Das62ae6792017-05-15 15:34:25 -07001018 targetSw, dstSw);
Saurav Das6430f412018-01-25 09:49:01 -08001019 updatedDevices.add(targetSw);
1020 updatedDevices.add(dstSw);
1021 } else {
1022 someFailed = true;
Saurav Das62ae6792017-05-15 15:34:25 -07001023 }
1024 }
1025 }
Saurav Das6430f412018-01-25 09:49:01 -08001026 if (!someFailed) {
1027 // here is where we update all devices not touched by this instance
1028 updatedEcmpSpgMap.keySet().stream()
1029 .filter(devId -> !updatedDevices.contains(devId))
1030 .forEach(devId -> {
1031 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1032 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1033 });
1034 }
Saurav Das62ae6792017-05-15 15:34:25 -07001035 }
1036
1037 /**
1038 * Edits hash groups in the src-switch (targetSw) of a route-path by
1039 * calling the groupHandler to either add or remove buckets in an existing
1040 * hash group.
1041 *
1042 * @param route a single list representing a route-path where the first element
1043 * is the src-switch (targetSw) of the route-path and the
1044 * second element is the dst-switch
1045 * @param revoke true if buckets in the hash-groups need to be removed;
1046 * false if buckets in the hash-groups need to be added
1047 * @return true if the hash group editing is successful
1048 */
1049 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1050 boolean revoke) {
1051 DeviceId targetSw = route.get(0);
1052 if (route.size() < 2) {
1053 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1054 return false;
1055 }
1056 DeviceId destSw = route.get(1);
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001057 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Das62ae6792017-05-15 15:34:25 -07001058 targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001059 // figure out the new next hops at the targetSw towards the destSw
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001060 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Das62ae6792017-05-15 15:34:25 -07001061 // call group handler to change hash group at targetSw
1062 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1063 if (grpHandler == null) {
1064 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1065 + " {} hash group buckets for route:{} ", targetSw,
1066 (revoke) ? "revoke" : "repopulate", route);
1067 return false;
1068 }
1069 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
1070 (revoke) ? "revoke" : "repopulating",
1071 targetSw, destSw, nextHops);
1072 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1073 destSw, true)
1074 : grpHandler.fixHashGroups(targetSw, nextHops,
1075 destSw, false);
1076 }
1077
1078 /**
Saurav Das261c3002017-06-13 15:35:54 -07001079 * Start the flow rule population process if it was never started. The
1080 * process finishes successfully when all flow rules are set and stops with
1081 * ABORTED status when any groups required for flows is not set yet.
Saurav Das62ae6792017-05-15 15:34:25 -07001082 */
Saurav Das261c3002017-06-13 15:35:54 -07001083 public void startPopulationProcess() {
1084 statusLock.lock();
1085 try {
1086 if (populationStatus == Status.IDLE
1087 || populationStatus == Status.SUCCEEDED
1088 || populationStatus == Status.ABORTED) {
1089 populateAllRoutingRules();
sangho28d0b6d2015-05-07 13:30:57 -07001090 } else {
Saurav Das261c3002017-06-13 15:35:54 -07001091 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1092 populationStatus);
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001093 }
Saurav Das261c3002017-06-13 15:35:54 -07001094 } finally {
1095 statusLock.unlock();
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001096 }
sanghofb7c7292015-04-13 15:15:58 -07001097 }
1098
Saurav Dasb149be12016-06-07 10:08:06 -07001099 /**
Saurav Das261c3002017-06-13 15:35:54 -07001100 * Revoke rules of given subnet in all edge switches.
1101 *
1102 * @param subnets subnet being removed
1103 * @return true if succeed
1104 */
1105 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1106 statusLock.lock();
1107 try {
Charles Chand66d6712018-03-29 16:03:41 -07001108 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1109 .map(Device::id)
1110 .filter(this::shouldProgram)
1111 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das261c3002017-06-13 15:35:54 -07001112 } finally {
1113 statusLock.unlock();
1114 }
1115 }
1116
1117 /**
Charles Chan910be6a2017-08-23 14:46:43 -07001118 * Populates IP rules for a route that has direct connection to the switch
1119 * if the current instance is the master of the switch.
1120 *
1121 * @param deviceId device ID of the device that next hop attaches to
1122 * @param prefix IP prefix of the route
1123 * @param hostMac MAC address of the next hop
1124 * @param hostVlanId Vlan ID of the nexthop
1125 * @param outPort port where the next hop attaches to
1126 */
1127 void populateRoute(DeviceId deviceId, IpPrefix prefix,
1128 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chand66d6712018-03-29 16:03:41 -07001129 if (shouldProgram(deviceId)) {
Charles Chan910be6a2017-08-23 14:46:43 -07001130 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1131 }
1132 }
1133
1134 /**
1135 * Removes IP rules for a route when the next hop is gone.
1136 * if the current instance is the master of the switch.
1137 *
1138 * @param deviceId device ID of the device that next hop attaches to
1139 * @param prefix IP prefix of the route
1140 * @param hostMac MAC address of the next hop
1141 * @param hostVlanId Vlan ID of the nexthop
1142 * @param outPort port that next hop attaches to
1143 */
1144 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1145 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chand66d6712018-03-29 16:03:41 -07001146 if (shouldProgram(deviceId)) {
Charles Chan910be6a2017-08-23 14:46:43 -07001147 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1148 }
1149 }
1150
Charles Chand66d6712018-03-29 16:03:41 -07001151 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1152 if (shouldProgram(deviceId)) {
1153 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1154 }
1155 }
1156
1157 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1158 if (shouldProgram(deviceId)) {
1159 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1160 }
1161 }
1162
1163 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1164 VlanId vlanId, boolean popVlan, boolean install) {
1165 if (shouldProgram(deviceId)) {
1166 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1167 }
1168 }
1169
1170 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1171 VlanId vlanId, boolean popVlan, boolean install) {
1172 if (shouldProgram(deviceId)) {
1173 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1174 vlanId, popVlan, install);
1175 }
1176 }
1177
Charles Chan910be6a2017-08-23 14:46:43 -07001178 /**
Jonghwan Hyun9aaa34f2018-04-09 09:40:50 -07001179 * Populates IP rules for a route when the next hop is double-tagged.
1180 *
1181 * @param deviceId device ID that next hop attaches to
1182 * @param prefix IP prefix of the route
1183 * @param hostMac MAC address of the next hop
1184 * @param innerVlan Inner Vlan ID of the next hop
1185 * @param outerVlan Outer Vlan ID of the next hop
1186 * @param outerTpid Outer TPID of the next hop
1187 * @param outPort port that the next hop attaches to
1188 */
1189 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1190 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1191 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1192 VlanId dummyVlan = srManager.allocateDummyVlanId(
1193 new ConnectPoint(deviceId, outPort), prefix.address());
1194 if (!dummyVlan.equals(VlanId.NONE)) {
1195 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1196 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1197 srManager.routingRulePopulator.processDoubleTaggedFilter(
1198 deviceId, outPort, outerVlan, innerVlan, true);
1199 } else {
1200 log.error("Failed to allocate dummy VLAN ID for host {} at {}/{}",
1201 prefix.address(), deviceId, outPort);
1202 }
1203 }
1204 }
1205
1206 /**
1207 * Revokes IP rules for a route when the next hop is double-tagged.
1208 *
1209 * @param deviceId device ID that next hop attaches to
1210 * @param prefix IP prefix of the route
1211 * @param hostMac MAC address of the next hop
1212 * @param innerVlan Inner Vlan ID of the next hop
1213 * @param outerVlan Outer Vlan ID of the next hop
1214 * @param outerTpid Outer TPID of the next hop
1215 * @param outPort port that the next hop attaches to
1216 */
1217 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1218 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1219 // Revoke route either if this node have the mastership (when device is available) or
1220 // if this node is the leader (even when device is unavailable)
1221 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1222 if (srManager.deviceService.isAvailable(deviceId)) {
1223 // Master node will revoke specified rule.
1224 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1225 return;
1226 }
1227
1228 // isLocalMaster will return false when the device is unavailable.
1229 // Verify if this node is the leader in that case.
1230 NodeId leader = srManager.leadershipService.runForLeadership(
1231 deviceId.toString()).leaderNodeId();
1232 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1233 // Leader node will revoke specified rule.
1234 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1235 return;
1236 }
1237 }
1238
1239 VlanId dummyVlan = srManager.dummyVlanIdStore().get(new DummyVlanIdStoreKey(
1240 new ConnectPoint(deviceId, outPort), prefix.address()));
1241 if (dummyVlan == null) {
1242 log.error("Failed to get dummyVlanId for host {} at {}/{}.",
1243 prefix.address(), deviceId, outPort);
1244 } else {
1245 srManager.routingRulePopulator.revokeDoubleTaggedRoute(
1246 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1247 srManager.routingRulePopulator.processDoubleTaggedFilter(
1248 deviceId, outPort, outerVlan, innerVlan, false);
1249 }
1250 }
1251
1252
1253 /**
Saurav Das261c3002017-06-13 15:35:54 -07001254 * Remove ECMP graph entry for the given device. Typically called when
1255 * device is no longer available.
1256 *
1257 * @param deviceId the device for which graphs need to be purged
1258 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001259 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Das6430f412018-01-25 09:49:01 -08001260 statusLock.lock();
1261 try {
Saurav Das6430f412018-01-25 09:49:01 -08001262 if (populationStatus == Status.STARTED) {
1263 log.warn("Previous rule population is not finished. Cannot"
1264 + " proceeed with purgeEcmpGraph for {}", deviceId);
1265 return;
1266 }
1267 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1268 currentEcmpSpgMap.remove(deviceId);
1269 if (updatedEcmpSpgMap != null) {
1270 updatedEcmpSpgMap.remove(deviceId);
1271 }
1272 } finally {
1273 statusLock.unlock();
Saurav Das261c3002017-06-13 15:35:54 -07001274 }
1275 }
1276
Saurav Das00e553b2018-04-21 17:19:48 -07001277 /**
1278 * Attempts a full reroute of route-paths if topology has changed relatively
1279 * close to a mastership change event. Does not do a reroute if mastership
1280 * change is due to reasons other than a ONOS cluster event - for example a
1281 * call to balance-masters, or a switch up/down event.
1282 *
1283 * @param devId the device identifier for which mastership has changed
1284 * @param me the mastership event
1285 */
1286 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1287 // give small delay to absorb mastership events that are caused by
1288 // device that has disconnected from cluster
Saurav Das49368392018-04-23 18:42:12 -07001289 executorServiceMstChg.schedule(new MasterChange(devId, me),
1290 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das00e553b2018-04-21 17:19:48 -07001291 }
1292
1293 protected final class MasterChange implements Runnable {
1294 private DeviceId devId;
1295 private MastershipEvent me;
1296 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1297 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
Saurav Dasec683dc2018-04-27 18:42:30 -07001298 private static final long EDGE_PORT_EVENT_THRESHOLD = 10000; //ms
Saurav Das00e553b2018-04-21 17:19:48 -07001299
1300 MasterChange(DeviceId devId, MastershipEvent me) {
1301 this.devId = devId;
1302 this.me = me;
1303 }
1304
1305 @Override
1306 public void run() {
1307 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1308 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1309
1310 // ignore event for lost switch if cluster event hasn't happened -
1311 // device down event will handle it
1312 if ((me.roleInfo().master() == null
1313 || !srManager.deviceService.isAvailable(devId))
1314 && !clusterEvent) {
1315 log.debug("Full reroute not required for lost device: {}/{} "
1316 + "clusterEvent/timeSince: {}/{}",
1317 devId, me.roleInfo(), clusterEvent, lce);
1318 return;
1319 }
1320
1321 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1322 long lde = Instant.now().toEpochMilli() - update;
1323 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1324
1325 // ignore event for recently connected switch if cluster event hasn't
1326 // happened - link up events will handle it
1327 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1328 && !clusterEvent) {
1329 log.debug("Full reroute not required for recently available"
1330 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1331 + "clusterEvent/timeSince: {}/{}",
1332 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1333 return;
1334 }
1335
Saurav Dasec683dc2018-04-27 18:42:30 -07001336 long lepe = Instant.now().toEpochMilli()
1337 - srManager.lastEdgePortEvent.toEpochMilli();
1338 boolean edgePortEvent = lepe < EDGE_PORT_EVENT_THRESHOLD;
1339
Saurav Das00e553b2018-04-21 17:19:48 -07001340 // if it gets here, then mastership change is likely due to onos
1341 // instance failure, or network partition in onos cluster
1342 // normally a mastership change like this does not require re-programming
1343 // but if topology changes happen at the same time then we may miss events
1344 if (!isRoutingStable() && clusterEvent) {
Saurav Dasec683dc2018-04-27 18:42:30 -07001345 log.warn("Mastership changed for dev: {}/{} while programming route-paths "
Saurav Das00e553b2018-04-21 17:19:48 -07001346 + "due to clusterEvent {} ms ago .. attempting full reroute",
1347 devId, me.roleInfo(), lce);
1348 if (srManager.mastershipService.isLocalMaster(devId)) {
1349 // old master could have died when populating filters
1350 populatePortAddressingRules(devId);
Saurav Das49368392018-04-23 18:42:12 -07001351 // old master could have died when creating groups
1352 srManager.purgeHashedNextObjectiveStore(devId);
Saurav Das00e553b2018-04-21 17:19:48 -07001353 }
Saurav Das00e553b2018-04-21 17:19:48 -07001354 // XXX right now we have no fine-grained way to only make changes
1355 // for the route paths affected by this device.
1356 populateAllRoutingRules();
Saurav Dasec683dc2018-04-27 18:42:30 -07001357
1358 } else if (edgePortEvent && clusterEvent) {
1359 log.warn("Mastership changed for dev: {}/{} due to clusterEvent {} ms ago "
1360 + "while edge-port event happened {} ms ago "
1361 + " .. reprogramming all edge-ports",
1362 devId, me.roleInfo(), lce, lepe);
1363 if (shouldProgram(devId)) {
1364 srManager.deviceService.getPorts(devId).stream()
1365 .filter(p -> srManager.interfaceService
1366 .isConfigured(new ConnectPoint(devId, p.number())))
1367 .forEach(p -> srManager.processPortUpdated(devId, p));
1368 }
1369
Saurav Das00e553b2018-04-21 17:19:48 -07001370 } else {
1371 log.debug("Stable route-paths .. full reroute not attempted for "
1372 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1373 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1374 deviceEvent, lde, clusterEvent, lce);
1375 }
1376 }
1377 }
1378
Saurav Das261c3002017-06-13 15:35:54 -07001379 //////////////////////////////////////
1380 // Routing helper methods and classes
1381 //////////////////////////////////////
1382
1383 /**
Saurav Das1b391d52016-11-29 14:27:25 -08001384 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb149be12016-06-07 10:08:06 -07001385 * previous ecmp shortest-path graph exists for a switch in order to compute
1386 * affected routes. If such a graph does not exist, the method returns null.
1387 *
1388 * @param linkFail the failed link
1389 * @return the set of affected routes which may be empty if no routes were
1390 * affected, or null if no previous ecmp spg was found for comparison
1391 */
sanghofb7c7292015-04-13 15:15:58 -07001392 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sanghofb7c7292015-04-13 15:15:58 -07001393 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1394
1395 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001396 log.debug("Computing the impacted routes for device {} due to link fail",
1397 sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001398 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001399 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001400 continue;
1401 }
Charles Chand66d6712018-03-29 16:03:41 -07001402 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001403 // check for mastership change since last run
1404 if (!lastProgrammed.contains(sw.id())) {
1405 lastProgrammed.add(sw.id());
1406 log.warn("New reponsibility for this node to program dev:{}"
1407 + " ... nuking current ECMPspg", sw.id());
1408 currentEcmpSpgMap.remove(sw.id());
1409 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001410 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1411 if (ecmpSpg == null) {
1412 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1413 + " rerouting and opting for full-reroute", rootSw);
1414 return null;
1415 }
1416 if (log.isDebugEnabled()) {
1417 log.debug("Root switch: {}", rootSw);
1418 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1419 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1420 }
1421 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1422 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1423 // figure out if the broken link affected any route-paths in this graph
1424 for (Integer itrIdx : switchVia.keySet()) {
1425 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1426 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1427 switchVia.get(itrIdx);
1428 for (DeviceId targetSw : swViaMap.keySet()) {
1429 log.trace("TargetSwitch {} --> RootSwitch {}",
1430 targetSw, rootSw);
Saurav Dasb149be12016-06-07 10:08:06 -07001431 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1432 log.trace(" Via:");
Pier Ventreadb4ae62016-11-23 09:57:42 -08001433 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb149be12016-06-07 10:08:06 -07001434 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001435 Set<ArrayList<DeviceId>> subLinks =
1436 computeLinks(targetSw, rootSw, swViaMap);
1437 for (ArrayList<DeviceId> alink: subLinks) {
1438 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1439 alink.get(1).equals(linkFail.dst().deviceId()))
1440 ||
1441 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1442 alink.get(1).equals(linkFail.src().deviceId()))) {
1443 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1444 ArrayList<DeviceId> aRoute = new ArrayList<>();
1445 aRoute.add(targetSw); // switch with rules to populate
1446 aRoute.add(rootSw); // towards this destination
1447 routes.add(aRoute);
1448 break;
1449 }
sanghofb7c7292015-04-13 15:15:58 -07001450 }
1451 }
1452 }
Saurav Dasfe0b05e2017-08-14 16:44:43 -07001453
sanghofb7c7292015-04-13 15:15:58 -07001454 }
sangho28d0b6d2015-05-07 13:30:57 -07001455
sanghofb7c7292015-04-13 15:15:58 -07001456 }
sanghofb7c7292015-04-13 15:15:58 -07001457 return routes;
1458 }
1459
Saurav Das1b391d52016-11-29 14:27:25 -08001460 /**
1461 * Computes set of affected routes due to new links or failed switches.
1462 *
Saurav Dasdc7f2752018-03-18 21:28:15 -07001463 * @param failedSwitch deviceId of failed switch if any
Saurav Das1b391d52016-11-29 14:27:25 -08001464 * @return the set of affected routes which may be empty if no routes were
1465 * affected
1466 */
Saurav Dascea556f2018-03-05 14:37:16 -08001467 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das261c3002017-06-13 15:35:54 -07001468 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das1b391d52016-11-29 14:27:25 -08001469 ImmutableSet.builder();
sanghofb7c7292015-04-13 15:15:58 -07001470
1471 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das261c3002017-06-13 15:35:54 -07001472 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chand66d6712018-03-29 16:03:41 -07001473 if (!shouldProgram(sw.id())) {
Saurav Das00e553b2018-04-21 17:19:48 -07001474 lastProgrammed.remove(sw.id());
sanghofb7c7292015-04-13 15:15:58 -07001475 continue;
1476 }
Charles Chand66d6712018-03-29 16:03:41 -07001477 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das261c3002017-06-13 15:35:54 -07001478 if (log.isTraceEnabled()) {
1479 log.trace("Device links for dev: {}", rootSw);
1480 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1481 log.trace("{} -> {} ", link.src().deviceId(),
1482 link.dst().deviceId());
1483 }
Saurav Dasb149be12016-06-07 10:08:06 -07001484 }
Saurav Das00e553b2018-04-21 17:19:48 -07001485 // check for mastership change since last run
1486 if (!lastProgrammed.contains(sw.id())) {
1487 lastProgrammed.add(sw.id());
1488 log.warn("New reponsibility for this node to program dev:{}"
1489 + " ... nuking current ECMPspg", sw.id());
1490 currentEcmpSpgMap.remove(sw.id());
1491 }
Saurav Das261c3002017-06-13 15:35:54 -07001492 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1493 if (currEcmpSpg == null) {
1494 log.debug("No existing ECMP graph for device {}.. adding self as "
1495 + "changed route", rootSw);
1496 changedRtBldr.add(Lists.newArrayList(rootSw));
1497 continue;
1498 }
1499 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Dasdebcf882018-04-06 20:16:01 -07001500 if (newEcmpSpg == null) {
1501 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1502 continue;
1503 }
Saurav Das261c3002017-06-13 15:35:54 -07001504 if (log.isDebugEnabled()) {
1505 log.debug("Root switch: {}", rootSw);
1506 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1507 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1508 }
1509 // first use the updated/new map to compare to current/existing map
1510 // as new links may have come up
1511 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1512 // then use the current/existing map to compare to updated/new map
1513 // as switch may have been removed
1514 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho28d0b6d2015-05-07 13:30:57 -07001515 }
Saurav Das1b391d52016-11-29 14:27:25 -08001516 }
sanghofb7c7292015-04-13 15:15:58 -07001517
Saurav Dascea556f2018-03-05 14:37:16 -08001518 // handle clearing state for a failed switch in case the switch does
1519 // not have a pair, or the pair is not available
1520 if (failedSwitch != null) {
Charles Chan6dbcd252018-04-02 11:46:38 -07001521 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1522 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dascea556f2018-03-05 14:37:16 -08001523 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1524 srManager.deviceService.getDevices().forEach(dev -> {
1525 if (!dev.id().equals(failedSwitch) &&
1526 srManager.mastershipService.isLocalMaster(dev.id())) {
1527 log.debug(" : {}", dev.id());
1528 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1529 }
1530 });
1531 }
1532 }
1533
Saurav Das261c3002017-06-13 15:35:54 -07001534 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das1b391d52016-11-29 14:27:25 -08001535 for (ArrayList<DeviceId> route: changedRoutes) {
1536 log.debug("Route changes Target -> Root");
1537 if (route.size() == 1) {
1538 log.debug(" : all -> {}", route.get(0));
1539 } else {
1540 log.debug(" : {} -> {}", route.get(0), route.get(1));
1541 }
1542 }
1543 return changedRoutes;
1544 }
1545
1546 /**
1547 * For the root switch, searches all the target nodes reachable in the base
1548 * graph, and compares paths to the ones in the comp graph.
1549 *
1550 * @param base the graph that is indexed for all reachable target nodes
1551 * from the root node
1552 * @param comp the graph that the base graph is compared to
1553 * @param rootSw both ecmp graphs are calculated for the root node
1554 * @return all the routes that have changed in the base graph
1555 */
1556 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1557 EcmpShortestPathGraph comp,
1558 DeviceId rootSw) {
1559 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1560 ImmutableSet.builder();
1561 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1562 base.getAllLearnedSwitchesAndVia();
1563 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1564 comp.getAllLearnedSwitchesAndVia();
1565 for (Integer itrIdx : baseMap.keySet()) {
1566 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1567 baseMap.get(itrIdx);
1568 for (DeviceId targetSw : baseViaMap.keySet()) {
1569 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1570 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1571 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Das62ae6792017-05-15 15:34:25 -07001572 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das1b391d52016-11-29 14:27:25 -08001573 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das261c3002017-06-13 15:35:54 -07001574 route.add(targetSw); // switch with rules to populate
1575 route.add(rootSw); // towards this destination
Saurav Das1b391d52016-11-29 14:27:25 -08001576 changedRoutesBuilder.add(route);
sanghofb7c7292015-04-13 15:15:58 -07001577 }
1578 }
sangho28d0b6d2015-05-07 13:30:57 -07001579 }
Saurav Das1b391d52016-11-29 14:27:25 -08001580 return changedRoutesBuilder.build();
sanghofb7c7292015-04-13 15:15:58 -07001581 }
1582
Saurav Das261c3002017-06-13 15:35:54 -07001583 /**
1584 * Returns the ECMP paths traversed to reach the target switch.
1585 *
1586 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1587 * @param targetSw the switch to reach from the root switch
1588 * @return the nodes traversed on ECMP paths to the target switch
1589 */
sanghofb7c7292015-04-13 15:15:58 -07001590 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das1b391d52016-11-29 14:27:25 -08001591 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sanghofb7c7292015-04-13 15:15:58 -07001592 for (Integer itrIdx : switchVia.keySet()) {
1593 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1594 switchVia.get(itrIdx);
Saurav Das1b391d52016-11-29 14:27:25 -08001595 if (swViaMap.get(targetSw) == null) {
sanghofb7c7292015-04-13 15:15:58 -07001596 continue;
1597 } else {
Saurav Das1b391d52016-11-29 14:27:25 -08001598 return swViaMap.get(targetSw);
sanghofb7c7292015-04-13 15:15:58 -07001599 }
1600 }
1601
Srikanth Vavilapalli64d96c12015-05-14 20:22:47 -07001602 return null;
sanghofb7c7292015-04-13 15:15:58 -07001603 }
1604
Saurav Das261c3002017-06-13 15:35:54 -07001605 /**
1606 * Utility method to break down a path from src to dst device into a collection
1607 * of links.
1608 *
1609 * @param src src device of the path
1610 * @param dst dst device of the path
1611 * @param viaMap path taken from src to dst device
1612 * @return collection of links in the path
1613 */
sanghofb7c7292015-04-13 15:15:58 -07001614 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1615 DeviceId dst,
1616 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1617 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1618 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1619 DeviceId linkSrc = src;
1620 DeviceId linkDst = dst;
1621 for (DeviceId viaDevice: via) {
1622 ArrayList<DeviceId> link = new ArrayList<>();
1623 linkDst = viaDevice;
1624 link.add(linkSrc);
1625 link.add(linkDst);
1626 subLinks.add(link);
1627 linkSrc = viaDevice;
1628 }
1629 ArrayList<DeviceId> link = new ArrayList<>();
1630 link.add(linkSrc);
1631 link.add(dst);
1632 subLinks.add(link);
1633 }
1634
1635 return subLinks;
1636 }
1637
Charles Chanc22cef32016-04-29 14:38:22 -07001638 /**
Charles Chand66d6712018-03-29 16:03:41 -07001639 * Determines whether this controller instance should program the
Saurav Das261c3002017-06-13 15:35:54 -07001640 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chand66d6712018-03-29 16:03:41 -07001641 * <p>
1642 * Once an instance is elected, it will be the only instance responsible for programming
1643 * both devices in the pair until it goes down.
Charles Chanc22cef32016-04-29 14:38:22 -07001644 *
Saurav Das261c3002017-06-13 15:35:54 -07001645 * @param deviceId device identifier to consider for routing
Charles Chand66d6712018-03-29 16:03:41 -07001646 * @return true if current instance should handle the routing for given device
Charles Chanc22cef32016-04-29 14:38:22 -07001647 */
Charles Chand66d6712018-03-29 16:03:41 -07001648 boolean shouldProgram(DeviceId deviceId) {
Charles Chanfbcb8812018-04-18 18:41:05 -07001649 Boolean cached = shouldProgramCache.get(deviceId);
1650 if (cached != null) {
Saurav Das00e553b2018-04-21 17:19:48 -07001651 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chanfbcb8812018-04-18 18:41:05 -07001652 return cached;
1653 }
1654
Charles Chand66d6712018-03-29 16:03:41 -07001655 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sangho80f11cb2015-04-01 13:05:26 -07001656
Charles Chand66d6712018-03-29 16:03:41 -07001657 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1658 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1659 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
1660 log.debug("Evaluate shouldProgram {}/pair={}. current={}, master={}, pairMaster={}",
1661 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1662
1663 // No pair device configured. Only handle when current instance is the master of the device
1664 if (!pairDeviceId.isPresent()) {
1665 log.debug("No pair device. current={}, master={}", currentNodeId, masterNodeId);
1666 return currentNodeId.equals(masterNodeId);
sangho80f11cb2015-04-01 13:05:26 -07001667 }
Charles Chand66d6712018-03-29 16:03:41 -07001668
1669 // Should not handle if current instance is not the master of either switch
1670 if (!currentNodeId.equals(masterNodeId) &&
1671 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
1672 log.debug("Current node {} is neither the master of target device {} nor pair device {}",
1673 currentNodeId, deviceId, pairDeviceId);
1674 return false;
1675 }
1676
1677 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1678
1679 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1680 if (v == null) {
1681 // There is no value in the map. Elect a node
1682 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1683 } else {
1684 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1685 // Use the node in the map if it is still alive and is a master of any of the two switches
1686 return v;
1687 } else {
1688 // Previously elected node is no longer the master of either switch. Re-elect a node.
1689 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1690 }
1691 }
1692 }));
1693
1694 if (king != null) {
1695 log.debug("{} should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001696 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chand66d6712018-03-29 16:03:41 -07001697 return king.equals(currentNodeId);
1698 } else {
1699 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chanfbcb8812018-04-18 18:41:05 -07001700 shouldProgramCache.remove(deviceId);
Charles Chand66d6712018-03-29 16:03:41 -07001701 return false;
1702 }
1703 }
1704
1705 /**
1706 * Elects a node who should take responsibility of programming devices.
1707 * @param nodeIds list of candidate node ID
1708 *
1709 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1710 */
1711 private NodeId elect(List<NodeId> nodeIds) {
1712 // Remove all null elements. This could happen when some device has no master
1713 nodeIds.removeAll(Collections.singleton(null));
1714 nodeIds.sort(null);
1715 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1716 }
1717
Charles Chanfbcb8812018-04-18 18:41:05 -07001718 void invalidateShouldProgramCache(DeviceId deviceId) {
1719 shouldProgramCache.remove(deviceId);
1720 }
1721
Charles Chand66d6712018-03-29 16:03:41 -07001722 /**
1723 * Returns a set of device ID, containing given device and its pair device if exist.
1724 *
1725 * @param deviceId Device ID
1726 * @return a set of device ID, containing given device and its pair device if exist.
1727 */
1728 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1729 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1730 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1731 return ret;
sangho80f11cb2015-04-01 13:05:26 -07001732 }
1733
Charles Chanc22cef32016-04-29 14:38:22 -07001734 /**
Saurav Das261c3002017-06-13 15:35:54 -07001735 * Returns the set of deviceIds which are the next hops from the targetSw
1736 * to the dstSw according to the latest ECMP spg.
1737 *
1738 * @param targetSw the switch for which the next-hops are desired
1739 * @param dstSw the switch to which the next-hops lead to from the targetSw
1740 * @return set of next hop deviceIds, could be empty if no next hops are found
1741 */
1742 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1743 boolean targetIsEdge = false;
1744 try {
1745 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1746 } catch (DeviceConfigNotFoundException e) {
1747 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1748 + "continuing to getNextHops", targetSw);
1749 }
1750
1751 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1752 if (ecmpSpg == null) {
1753 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1754 return ImmutableSet.of();
1755 }
1756 HashMap<Integer,
1757 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1758 ecmpSpg.getAllLearnedSwitchesAndVia();
1759 for (Integer itrIdx : switchVia.keySet()) {
1760 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1761 switchVia.get(itrIdx);
1762 for (DeviceId target : swViaMap.keySet()) {
1763 if (!target.equals(targetSw)) {
1764 continue;
1765 }
Saurav Das49368392018-04-23 18:42:12 -07001766 // optimization for spines to not use leaves to get
1767 // to a spine or other leaves. Also leaves should not use other
1768 // leaves to get to the destination
1769 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Das97241862018-02-14 14:14:54 -08001770 boolean pathdevIsEdge = false;
1771 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Das49368392018-04-23 18:42:12 -07001772 log.debug("Evaluating next-hop in path: {}", via);
Saurav Das97241862018-02-14 14:14:54 -08001773 for (DeviceId pathdev : via) {
1774 try {
1775 pathdevIsEdge = srManager.deviceConfiguration
1776 .isEdgeDevice(pathdev);
1777 } catch (DeviceConfigNotFoundException e) {
1778 log.warn(e.getMessage());
1779 }
1780 if (pathdevIsEdge) {
1781 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1782 + " --> dstSw:{} which goes through an edge"
1783 + " device {} in path {}", itrIdx,
1784 targetSw, dstSw, pathdev, via);
1785 return ImmutableSet.of();
1786 }
1787 }
1788 }
Saurav Das261c3002017-06-13 15:35:54 -07001789 }
1790 Set<DeviceId> nextHops = new HashSet<>();
1791 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1792 if (via.isEmpty()) {
1793 // the dstSw is the next-hop from the targetSw
1794 nextHops.add(dstSw);
1795 } else {
1796 // first elem is next-hop in each ECMP path
1797 nextHops.add(via.get(0));
1798 }
1799 }
Saurav Das49368392018-04-23 18:42:12 -07001800 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
1801 dstSw, nextHops);
Saurav Das261c3002017-06-13 15:35:54 -07001802 return nextHops;
1803 }
1804 }
Saurav Das49368392018-04-23 18:42:12 -07001805 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das261c3002017-06-13 15:35:54 -07001806 return ImmutableSet.of(); //no next-hops found
1807 }
1808
Saurav Das261c3002017-06-13 15:35:54 -07001809 //////////////////////////////////////
1810 // Filtering rule creation
1811 //////////////////////////////////////
1812
1813 /**
Saurav Dasf9332192017-02-18 14:05:44 -08001814 * Populates filtering rules for port, and punting rules
1815 * for gateway IPs, loopback IPs and arp/ndp traffic.
1816 * Should only be called by the master instance for this device/port.
sangho80f11cb2015-04-01 13:05:26 -07001817 *
1818 * @param deviceId Switch ID to set the rules
1819 */
Charles Chanfbcb8812018-04-18 18:41:05 -07001820 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das07c74602016-04-27 18:35:50 -07001821 // Although device is added, sometimes device store does not have the
1822 // ports for this device yet. It results in missing filtering rules in the
1823 // switch. We will attempt it a few times. If it still does not work,
1824 // user can manually repopulate using CLI command sr-reroute-network
Charles Chan18fa4252017-02-08 16:10:40 -08001825 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001826 if (firstRun == null) {
1827 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das07c74602016-04-27 18:35:50 -07001828 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001829 executorService.schedule(new RetryFilters(deviceId, firstRun),
1830 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sangho80f11cb2015-04-01 13:05:26 -07001831 }
1832
1833 /**
Saurav Dasd1872b02016-12-02 15:43:47 -08001834 * RetryFilters populates filtering objectives for a device and keeps retrying
1835 * till the number of ports filtered are constant for a predefined number
1836 * of attempts.
1837 */
1838 protected final class RetryFilters implements Runnable {
1839 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1840 DeviceId devId;
1841 int counter;
1842 PortFilterInfo prevRun;
1843
1844 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das07c74602016-04-27 18:35:50 -07001845 devId = deviceId;
Saurav Dasd1872b02016-12-02 15:43:47 -08001846 prevRun = previousRun;
1847 counter = 0;
Saurav Das07c74602016-04-27 18:35:50 -07001848 }
1849
1850 @Override
1851 public void run() {
Charles Chan077314e2017-06-22 14:27:17 -07001852 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chan18fa4252017-02-08 16:10:40 -08001853 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd1872b02016-12-02 15:43:47 -08001854 boolean sameResult = prevRun.equals(thisRun);
1855 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1856 thisRun, sameResult);
Ray Milkey614352e2018-02-26 09:36:31 -08001857 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Dasf9332192017-02-18 14:05:44 -08001858 // exponentially increasing intervals for retries
1859 executorService.schedule(this,
1860 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1861 TimeUnit.MILLISECONDS);
Saurav Dasd1872b02016-12-02 15:43:47 -08001862 if (!sameResult) {
1863 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1864 }
Saurav Das07c74602016-04-27 18:35:50 -07001865 }
Saurav Dasd1872b02016-12-02 15:43:47 -08001866 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das07c74602016-04-27 18:35:50 -07001867 }
Saurav Das07c74602016-04-27 18:35:50 -07001868 }
sangho80f11cb2015-04-01 13:05:26 -07001869}