blob: eb0af6772b85471ef63805a5d75ee0db60918c74 [file] [log] [blame]
sanghob35a6192015-04-01 13:05:26 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
sanghob35a6192015-04-01 13:05:26 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.segmentrouting;
17
Saurav Dasd2fded02016-12-02 15:43:47 -080018import com.google.common.base.MoreObjects;
Saurav Dasc88d4662017-05-15 15:34:25 -070019import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableMap.Builder;
Charles Chan93e71ba2016-04-29 14:38:22 -070021import com.google.common.collect.ImmutableSet;
Saurav Das4e3224f2016-11-29 14:27:25 -080022import com.google.common.collect.Lists;
sangho20eff1d2015-04-13 15:15:58 -070023import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
Saurav Dasceccf242017-08-03 18:30:35 -070025
Jonghwan Hyun800d9d02018-04-09 09:40:50 -070026import org.onlab.packet.EthType;
sangho666cd6d2015-04-14 16:27:13 -070027import org.onlab.packet.Ip4Address;
Pier Ventree0ae7a32016-11-23 09:57:42 -080028import org.onlab.packet.Ip6Address;
sanghob35a6192015-04-01 13:05:26 -070029import org.onlab.packet.IpPrefix;
Charles Chan2fde6d42017-08-23 14:46:43 -070030import org.onlab.packet.MacAddress;
31import org.onlab.packet.VlanId;
Saurav Das7bcbe702017-06-13 15:35:54 -070032import org.onosproject.cluster.NodeId;
Saurav Das201762d2018-04-21 17:19:48 -070033import org.onosproject.mastership.MastershipEvent;
Charles Chan93e71ba2016-04-29 14:38:22 -070034import org.onosproject.net.ConnectPoint;
sanghob35a6192015-04-01 13:05:26 -070035import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
sangho20eff1d2015-04-13 15:15:58 -070037import org.onosproject.net.Link;
Charles Chan2fde6d42017-08-23 14:46:43 -070038import org.onosproject.net.PortNumber;
Charles Chan0b4e6182015-11-03 10:42:14 -080039import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
40import org.onosproject.segmentrouting.config.DeviceConfiguration;
Saurav Dasc88d4662017-05-15 15:34:25 -070041import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
Jonghwan Hyun800d9d02018-04-09 09:40:50 -070042import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
Charles Chan2ff1bac2018-03-29 16:03:41 -070043import org.onosproject.store.serializers.KryoNamespaces;
44import org.onosproject.store.service.Serializer;
sanghob35a6192015-04-01 13:05:26 -070045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070048import java.time.Instant;
sanghob35a6192015-04-01 13:05:26 -070049import java.util.ArrayList;
Charles Chan2ff1bac2018-03-29 16:03:41 -070050import java.util.Collections;
sanghob35a6192015-04-01 13:05:26 -070051import java.util.HashMap;
52import java.util.HashSet;
Saurav Das7bcbe702017-06-13 15:35:54 -070053import java.util.Iterator;
Charles Chan2ff1bac2018-03-29 16:03:41 -070054import java.util.List;
Saurav Das7bcbe702017-06-13 15:35:54 -070055import java.util.Map;
Saurav Dasd2fded02016-12-02 15:43:47 -080056import java.util.Objects;
Charles Chanba6c5752018-04-02 11:46:38 -070057import java.util.Optional;
sanghob35a6192015-04-01 13:05:26 -070058import java.util.Set;
Saurav Das59232cf2016-04-27 18:35:50 -070059import java.util.concurrent.ScheduledExecutorService;
60import java.util.concurrent.TimeUnit;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090061import java.util.concurrent.locks.Lock;
62import java.util.concurrent.locks.ReentrantLock;
Saurav Das604ab3a2018-03-18 21:28:15 -070063import java.util.stream.Stream;
64
Saurav Dasd2fded02016-12-02 15:43:47 -080065import static com.google.common.base.MoreObjects.toStringHelper;
Pier Ventree0ae7a32016-11-23 09:57:42 -080066import static com.google.common.base.Preconditions.checkNotNull;
67import static java.util.concurrent.Executors.newScheduledThreadPool;
68import static org.onlab.util.Tools.groupedThreads;
sanghob35a6192015-04-01 13:05:26 -070069
Charles Chane849c192016-01-11 18:28:54 -080070/**
71 * Default routing handler that is responsible for route computing and
72 * routing rule population.
73 */
sanghob35a6192015-04-01 13:05:26 -070074public class DefaultRoutingHandler {
Saurav Das018605f2017-02-18 14:05:44 -080075 private static final int MAX_CONSTANT_RETRY_ATTEMPTS = 5;
Ray Milkey3717e602018-02-01 13:49:47 -080076 private static final long RETRY_INTERVAL_MS = 250L;
Saurav Das018605f2017-02-18 14:05:44 -080077 private static final int RETRY_INTERVAL_SCALE = 1;
Saurav Dasceccf242017-08-03 18:30:35 -070078 private static final long STABLITY_THRESHOLD = 10; //secs
Saurav Das201762d2018-04-21 17:19:48 -070079 private static final long MASTER_CHANGE_DELAY = 1000; // ms
Charles Chan93e71ba2016-04-29 14:38:22 -070080 private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
sanghob35a6192015-04-01 13:05:26 -070081
82 private SegmentRoutingManager srManager;
83 private RoutingRulePopulator rulePopulator;
Shashikanth VH013a7bc2015-12-11 01:32:44 +053084 private HashMap<DeviceId, EcmpShortestPathGraph> currentEcmpSpgMap;
85 private HashMap<DeviceId, EcmpShortestPathGraph> updatedEcmpSpgMap;
sangho666cd6d2015-04-14 16:27:13 -070086 private DeviceConfiguration config;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +090087 private final Lock statusLock = new ReentrantLock();
88 private volatile Status populationStatus;
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070089 private ScheduledExecutorService executorService
Saurav Dasd2fded02016-12-02 15:43:47 -080090 = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
Saurav Dasc6ff8f02018-04-23 18:42:12 -070091 private ScheduledExecutorService executorServiceMstChg
92 = newScheduledThreadPool(1, groupedThreads("masterChg", "mstch-%d", log));
93
Saurav Das201762d2018-04-21 17:19:48 -070094 private Instant lastRoutingChange = Instant.EPOCH;
sanghob35a6192015-04-01 13:05:26 -070095
Saurav Das201762d2018-04-21 17:19:48 -070096 // Distributed store to keep track of ONOS instance that should program the
97 // device pair. There should be only one instance (the king) that programs the same pair.
Charles Chan2ff1bac2018-03-29 16:03:41 -070098 Map<Set<DeviceId>, NodeId> shouldProgram;
Charles Chan50bb6ef2018-04-18 18:41:05 -070099 Map<DeviceId, Boolean> shouldProgramCache;
Charles Chan2ff1bac2018-03-29 16:03:41 -0700100
Saurav Das201762d2018-04-21 17:19:48 -0700101 // Local store to keep track of all devices that this instance was responsible
102 // for programming in the last run. Helps to determine if mastership changed
103 // during a run - only relevant for programming as a result of topo change.
104 Set<DeviceId> lastProgrammed;
105
sanghob35a6192015-04-01 13:05:26 -0700106 /**
107 * Represents the default routing population status.
108 */
109 public enum Status {
110 // population process is not started yet.
111 IDLE,
112
113 // population process started.
114 STARTED,
115
Srikanth Vavilapallif5b234a2015-04-21 13:04:13 -0700116 // population process was aborted due to errors, mostly for groups not
117 // found.
sanghob35a6192015-04-01 13:05:26 -0700118 ABORTED,
119
120 // population process was finished successfully.
121 SUCCEEDED
122 }
123
124 /**
125 * Creates a DefaultRoutingHandler object.
126 *
127 * @param srManager SegmentRoutingManager object
128 */
Charles Chan2ff1bac2018-03-29 16:03:41 -0700129 DefaultRoutingHandler(SegmentRoutingManager srManager) {
Charles Chan50bb6ef2018-04-18 18:41:05 -0700130 this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
131 .withName("sr-should-program")
132 .withSerializer(Serializer.using(KryoNamespaces.API))
133 .withRelaxedReadConsistency()
134 .build().asJavaMap();
135 this.shouldProgramCache = Maps.newConcurrentMap();
136 update(srManager);
137 }
138
139 /**
140 * Updates a DefaultRoutingHandler object.
141 *
142 * @param srManager SegmentRoutingManager object
143 */
144 void update(SegmentRoutingManager srManager) {
sanghob35a6192015-04-01 13:05:26 -0700145 this.srManager = srManager;
146 this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
sangho666cd6d2015-04-14 16:27:13 -0700147 this.config = checkNotNull(srManager.deviceConfiguration);
sanghob35a6192015-04-01 13:05:26 -0700148 this.populationStatus = Status.IDLE;
sangho20eff1d2015-04-13 15:15:58 -0700149 this.currentEcmpSpgMap = Maps.newHashMap();
Saurav Das201762d2018-04-21 17:19:48 -0700150 this.lastProgrammed = Sets.newConcurrentHashSet();
sanghob35a6192015-04-01 13:05:26 -0700151 }
152
153 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700154 * Returns an immutable copy of the current ECMP shortest-path graph as
155 * computed by this controller instance.
156 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700157 * @return immutable copy of the current ECMP graph
Saurav Dasc88d4662017-05-15 15:34:25 -0700158 */
159 public ImmutableMap<DeviceId, EcmpShortestPathGraph> getCurrentEmcpSpgMap() {
160 Builder<DeviceId, EcmpShortestPathGraph> builder = ImmutableMap.builder();
161 currentEcmpSpgMap.entrySet().forEach(entry -> {
162 if (entry.getValue() != null) {
163 builder.put(entry.getKey(), entry.getValue());
164 }
165 });
166 return builder.build();
167 }
168
Saurav Dasceccf242017-08-03 18:30:35 -0700169 /**
170 * Acquires the lock used when making routing changes.
171 */
172 public void acquireRoutingLock() {
173 statusLock.lock();
174 }
175
176 /**
177 * Releases the lock used when making routing changes.
178 */
179 public void releaseRoutingLock() {
180 statusLock.unlock();
181 }
182
183 /**
184 * Determines if routing in the network has been stable in the last
185 * STABLITY_THRESHOLD seconds, by comparing the current time to the last
186 * routing change timestamp.
187 *
188 * @return true if stable
189 */
190 public boolean isRoutingStable() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700191 long last = (long) (lastRoutingChange.toEpochMilli() / 1000.0);
192 long now = (long) (Instant.now().toEpochMilli() / 1000.0);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700193 log.trace("Routing stable since {}s", now - last);
Saurav Dasceccf242017-08-03 18:30:35 -0700194 return (now - last) > STABLITY_THRESHOLD;
195 }
196
Saurav Dasc6ff8f02018-04-23 18:42:12 -0700197 /**
198 * Gracefully shuts down the defaultRoutingHandler. Typically called when
199 * the app is deactivated
200 */
201 public void shutdown() {
202 executorService.shutdown();
203 executorServiceMstChg.shutdown();
204 }
Saurav Dasceccf242017-08-03 18:30:35 -0700205
Saurav Das7bcbe702017-06-13 15:35:54 -0700206 //////////////////////////////////////
207 // Route path handling
208 //////////////////////////////////////
209
Saurav Das45f48152018-01-18 12:07:33 -0800210 /* The following three methods represent the three major ways in which
211 * route-path handling is triggered in the network
Saurav Das7bcbe702017-06-13 15:35:54 -0700212 * a) due to configuration change
213 * b) due to route-added event
214 * c) due to change in the topology
215 */
216
Saurav Dasc88d4662017-05-15 15:34:25 -0700217 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700218 * Populates all routing rules to all switches. Typically triggered at
219 * startup or after a configuration event.
sanghob35a6192015-04-01 13:05:26 -0700220 */
Saurav Dasc88d4662017-05-15 15:34:25 -0700221 public void populateAllRoutingRules() {
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700222 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900223 statusLock.lock();
224 try {
Saurav Das7bcbe702017-06-13 15:35:54 -0700225 if (populationStatus == Status.STARTED) {
226 log.warn("Previous rule population is not finished. Cannot"
227 + " proceed with populateAllRoutingRules");
228 return;
229 }
230
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900231 populationStatus = Status.STARTED;
232 rulePopulator.resetCounter();
Saurav Das7bcbe702017-06-13 15:35:54 -0700233 log.info("Starting to populate all routing rules");
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900234 log.debug("populateAllRoutingRules: populationStatus is STARTED");
sanghob35a6192015-04-01 13:05:26 -0700235
Saurav Das7bcbe702017-06-13 15:35:54 -0700236 // take a snapshot of the topology
237 updatedEcmpSpgMap = new HashMap<>();
238 Set<EdgePair> edgePairs = new HashSet<>();
239 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800240 for (DeviceId dstSw : srManager.deviceConfiguration.getRouters()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700241 EcmpShortestPathGraph ecmpSpgUpdated =
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800242 new EcmpShortestPathGraph(dstSw, srManager);
243 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700244 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
245 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700246 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700247 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
248 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
249 edgePairs.add(new EdgePair(dstSw, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700250 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700251
252 if (!shouldProgram(dstSw)) {
Saurav Das201762d2018-04-21 17:19:48 -0700253 lastProgrammed.remove(dstSw);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900254 continue;
Saurav Das201762d2018-04-21 17:19:48 -0700255 } else {
256 lastProgrammed.add(dstSw);
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900257 }
Saurav Das201762d2018-04-21 17:19:48 -0700258 // To do a full reroute, assume all route-paths have changed
Charles Chan2ff1bac2018-03-29 16:03:41 -0700259 for (DeviceId dev : deviceAndItsPair(dstSw)) {
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800260 for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
261 if (targetSw.equals(dev)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700262 continue;
263 }
Jonathan Hart8ca2bc02017-11-30 18:23:42 -0800264 routeChanges.add(Lists.newArrayList(targetSw, dev));
Saurav Das7bcbe702017-06-13 15:35:54 -0700265 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900266 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700267 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900268
Saurav Das7bcbe702017-06-13 15:35:54 -0700269 if (!redoRouting(routeChanges, edgePairs, null)) {
270 log.debug("populateAllRoutingRules: populationStatus is ABORTED");
271 populationStatus = Status.ABORTED;
272 log.warn("Failed to repopulate all routing rules.");
273 return;
sanghob35a6192015-04-01 13:05:26 -0700274 }
275
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900276 log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED");
277 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700278 log.info("Completed all routing rule population. Total # of rules pushed : {}",
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900279 rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700280 return;
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900281 } finally {
282 statusLock.unlock();
sanghob35a6192015-04-01 13:05:26 -0700283 }
sanghob35a6192015-04-01 13:05:26 -0700284 }
285
sangho20eff1d2015-04-13 15:15:58 -0700286 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700287 * Populate rules from all other edge devices to the connect-point(s)
288 * specified for the given subnets.
289 *
290 * @param cpts connect point(s) of the subnets being added
291 * @param subnets subnets being added
Charles Chan2fde6d42017-08-23 14:46:43 -0700292 */
293 // XXX refactor
Saurav Das7bcbe702017-06-13 15:35:54 -0700294 protected void populateSubnet(Set<ConnectPoint> cpts, Set<IpPrefix> subnets) {
Charles Chan71e64f12017-09-11 15:21:57 -0700295 if (cpts == null || cpts.size() < 1 || cpts.size() > 2) {
296 log.warn("Skipping populateSubnet due to illegal size of connect points. {}", cpts);
297 return;
298 }
299
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700300 lastRoutingChange = Instant.now();
Saurav Das7bcbe702017-06-13 15:35:54 -0700301 statusLock.lock();
302 try {
303 if (populationStatus == Status.STARTED) {
304 log.warn("Previous rule population is not finished. Cannot"
305 + " proceed with routing rules for added routes");
306 return;
307 }
308 populationStatus = Status.STARTED;
309 rulePopulator.resetCounter();
Charles Chan2fde6d42017-08-23 14:46:43 -0700310 log.info("Starting to populate routing rules for added routes, subnets={}, cpts={}",
311 subnets, cpts);
Saurav Dasc568c342018-01-25 09:49:01 -0800312 // In principle an update to a subnet/prefix should not require a
313 // new ECMPspg calculation as it is not a topology event. As a
314 // result, we use the current/existing ECMPspg in the updated map
315 // used by the redoRouting method.
Saurav Das15a81782018-02-09 09:15:03 -0800316 if (updatedEcmpSpgMap == null) {
317 updatedEcmpSpgMap = new HashMap<>();
318 }
Saurav Dasc568c342018-01-25 09:49:01 -0800319 currentEcmpSpgMap.entrySet().forEach(entry -> {
320 updatedEcmpSpgMap.put(entry.getKey(), entry.getValue());
Saurav Dase7f51012018-02-09 17:26:45 -0800321 if (log.isTraceEnabled()) {
322 log.trace("Root switch: {}", entry.getKey());
323 log.trace(" Current/Existing SPG: {}", entry.getValue());
Saurav Dasc568c342018-01-25 09:49:01 -0800324 }
325 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700326 Set<EdgePair> edgePairs = new HashSet<>();
327 Set<ArrayList<DeviceId>> routeChanges = new HashSet<>();
328 boolean handleRouting = false;
329
330 if (cpts.size() == 2) {
331 // ensure connect points are edge-pairs
332 Iterator<ConnectPoint> iter = cpts.iterator();
333 DeviceId dev1 = iter.next().deviceId();
Charles Chanba6c5752018-04-02 11:46:38 -0700334 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dev1);
335 if (pairDev.isPresent() && iter.next().deviceId().equals(pairDev.get())) {
336 edgePairs.add(new EdgePair(dev1, pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700337 } else {
338 log.warn("Connectpoints {} for subnets {} not on "
339 + "pair-devices.. aborting populateSubnet", cpts, subnets);
340 populationStatus = Status.ABORTED;
341 return;
342 }
343 for (ConnectPoint cp : cpts) {
Saurav Dasc568c342018-01-25 09:49:01 -0800344 if (updatedEcmpSpgMap.get(cp.deviceId()) == null) {
345 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700346 new EcmpShortestPathGraph(cp.deviceId(), srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800347 updatedEcmpSpgMap.put(cp.deviceId(), ecmpSpgUpdated);
348 log.warn("populateSubnet: no updated graph for dev:{}"
349 + " ... creating", cp.deviceId());
350 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700351 if (!shouldProgram(cp.deviceId())) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700352 continue;
353 }
354 handleRouting = true;
355 }
356 } else {
357 // single connect point
358 DeviceId dstSw = cpts.iterator().next().deviceId();
Saurav Dasc568c342018-01-25 09:49:01 -0800359 if (updatedEcmpSpgMap.get(dstSw) == null) {
360 EcmpShortestPathGraph ecmpSpgUpdated =
Saurav Das7bcbe702017-06-13 15:35:54 -0700361 new EcmpShortestPathGraph(dstSw, srManager);
Saurav Dasc568c342018-01-25 09:49:01 -0800362 updatedEcmpSpgMap.put(dstSw, ecmpSpgUpdated);
363 log.warn("populateSubnet: no updated graph for dev:{}"
364 + " ... creating", dstSw);
365 }
Charles Chan2ff1bac2018-03-29 16:03:41 -0700366 handleRouting = shouldProgram(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700367 }
368
369 if (!handleRouting) {
370 log.debug("This instance is not handling ecmp routing to the "
371 + "connectPoint(s) {}", cpts);
372 populationStatus = Status.ABORTED;
373 return;
374 }
375
376 // if it gets here, this instance should handle routing for the
377 // connectpoint(s). Assume all route-paths have to be updated to
378 // the connectpoint(s) with the following exceptions
379 // 1. if target is non-edge no need for routing rules
380 // 2. if target is one of the connectpoints
381 for (ConnectPoint cp : cpts) {
382 DeviceId dstSw = cp.deviceId();
383 for (Device targetSw : srManager.deviceService.getDevices()) {
384 boolean isEdge = false;
385 try {
386 isEdge = config.isEdgeDevice(targetSw.id());
387 } catch (DeviceConfigNotFoundException e) {
Charles Chan92726132018-02-16 17:20:54 -0800388 log.warn(e.getMessage() + "aborting populateSubnet on targetSw {}", targetSw.id());
389 continue;
Saurav Das7bcbe702017-06-13 15:35:54 -0700390 }
Charles Chanba6c5752018-04-02 11:46:38 -0700391 Optional<DeviceId> pairDev = srManager.getPairDeviceId(dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -0700392 if (dstSw.equals(targetSw.id()) || !isEdge ||
Charles Chanba6c5752018-04-02 11:46:38 -0700393 (cpts.size() == 2 && pairDev.isPresent() && targetSw.id().equals(pairDev.get()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700394 continue;
395 }
396 routeChanges.add(Lists.newArrayList(targetSw.id(), dstSw));
397 }
398 }
399
400 if (!redoRouting(routeChanges, edgePairs, subnets)) {
401 log.debug("populateSubnet: populationStatus is ABORTED");
402 populationStatus = Status.ABORTED;
403 log.warn("Failed to repopulate the rules for subnet.");
404 return;
405 }
406
407 log.debug("populateSubnet: populationStatus is SUCCEEDED");
408 populationStatus = Status.SUCCEEDED;
409 log.info("Completed subnet population. Total # of rules pushed : {}",
410 rulePopulator.getCounter());
411 return;
412
413 } finally {
414 statusLock.unlock();
415 }
416 }
417
418 /**
Saurav Dasc88d4662017-05-15 15:34:25 -0700419 * Populates the routing rules or makes hash group changes according to the
420 * route-path changes due to link failure, switch failure or link up. This
421 * method should only be called for one of these three possible event-types.
Saurav Das604ab3a2018-03-18 21:28:15 -0700422 * Note that when a switch goes away, all of its links fail as well, but
423 * this is handled as a single switch removal event.
sangho20eff1d2015-04-13 15:15:58 -0700424 *
Saurav Das604ab3a2018-03-18 21:28:15 -0700425 * @param linkDown the single failed link, or null for other conditions such
426 * as link-up or a removed switch
Saurav Dasc88d4662017-05-15 15:34:25 -0700427 * @param linkUp the single link up, or null for other conditions such as
Saurav Das604ab3a2018-03-18 21:28:15 -0700428 * link-down or a removed switch
429 * @param switchDown the removed switch, or null for other conditions such
430 * as link-down or link-up
431 * @param seenBefore true if this event is for a linkUp or linkDown for a
432 * seen link
433 */
434 // TODO This method should be refactored into three separated methods
Saurav Dasc88d4662017-05-15 15:34:25 -0700435 public void populateRoutingRulesForLinkStatusChange(Link linkDown,
436 Link linkUp,
Saurav Das604ab3a2018-03-18 21:28:15 -0700437 DeviceId switchDown,
438 boolean seenBefore) {
439 if (Stream.of(linkDown, linkUp, switchDown).filter(Objects::nonNull)
440 .count() != 1) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700441 log.warn("Only one event can be handled for link status change .. aborting");
442 return;
443 }
Saurav Das604ab3a2018-03-18 21:28:15 -0700444
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700445 lastRoutingChange = Instant.now();
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900446 statusLock.lock();
447 try {
sangho20eff1d2015-04-13 15:15:58 -0700448
449 if (populationStatus == Status.STARTED) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700450 log.warn("Previous rule population is not finished. Cannot"
Saurav Dasc568c342018-01-25 09:49:01 -0800451 + " proceeed with routingRules for Topology change");
Saurav Dasc88d4662017-05-15 15:34:25 -0700452 return;
sangho20eff1d2015-04-13 15:15:58 -0700453 }
454
Saurav Das7bcbe702017-06-13 15:35:54 -0700455 // Take snapshots of the topology
sangho45b009c2015-05-07 13:30:57 -0700456 updatedEcmpSpgMap = new HashMap<>();
Saurav Das7bcbe702017-06-13 15:35:54 -0700457 Set<EdgePair> edgePairs = new HashSet<>();
sangho45b009c2015-05-07 13:30:57 -0700458 for (Device sw : srManager.deviceService.getDevices()) {
Shashikanth VH013a7bc2015-12-11 01:32:44 +0530459 EcmpShortestPathGraph ecmpSpgUpdated =
460 new EcmpShortestPathGraph(sw.id(), srManager);
sangho45b009c2015-05-07 13:30:57 -0700461 updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated);
Charles Chanba6c5752018-04-02 11:46:38 -0700462 Optional<DeviceId> pairDev = srManager.getPairDeviceId(sw.id());
463 if (pairDev.isPresent()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700464 // pairDev may not be available yet, but we still need to add
Charles Chanba6c5752018-04-02 11:46:38 -0700465 ecmpSpgUpdated = new EcmpShortestPathGraph(pairDev.get(), srManager);
466 updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
467 edgePairs.add(new EdgePair(sw.id(), pairDev.get()));
Saurav Das7bcbe702017-06-13 15:35:54 -0700468 }
sangho45b009c2015-05-07 13:30:57 -0700469 }
470
Saurav Dasc568c342018-01-25 09:49:01 -0800471 log.info("Starting to populate routing rules from Topology change");
sangho52abe3a2015-05-05 14:13:34 -0700472
sangho20eff1d2015-04-13 15:15:58 -0700473 Set<ArrayList<DeviceId>> routeChanges;
Saurav Dasc88d4662017-05-15 15:34:25 -0700474 log.debug("populateRoutingRulesForLinkStatusChange: "
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700475 + "populationStatus is STARTED");
sangho20eff1d2015-04-13 15:15:58 -0700476 populationStatus = Status.STARTED;
Saurav Dasc568c342018-01-25 09:49:01 -0800477 rulePopulator.resetCounter(); //XXX maybe useful to have a rehash ctr
478 boolean hashGroupsChanged = false;
Saurav Das4e3224f2016-11-29 14:27:25 -0800479 // try optimized re-routing
Saurav Dasc88d4662017-05-15 15:34:25 -0700480 if (linkDown == null) {
481 // either a linkUp or a switchDown - compute all route changes by
482 // comparing all routes of existing ECMP SPG to new ECMP SPG
Saurav Dase0d4c872018-03-05 14:37:16 -0800483 routeChanges = computeRouteChange(switchDown);
Saurav Dasc88d4662017-05-15 15:34:25 -0700484
Saurav Das9df5b7c2017-08-14 16:44:43 -0700485 // deal with linkUp of a seen-before link
Saurav Das604ab3a2018-03-18 21:28:15 -0700486 if (linkUp != null && seenBefore) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700487 // link previously seen before
488 // do hash-bucket changes instead of a re-route
489 processHashGroupChange(routeChanges, false, null);
490 // clear out routesChanges so a re-route is not attempted
491 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800492 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700493 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700494 // for a linkUp of a never-seen-before link
495 // let it fall through to a reroute of the routeChanges
Saurav Dasc88d4662017-05-15 15:34:25 -0700496
Saurav Das9df5b7c2017-08-14 16:44:43 -0700497 //deal with switchDown
498 if (switchDown != null) {
499 processHashGroupChange(routeChanges, true, switchDown);
500 // clear out routesChanges so a re-route is not attempted
501 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800502 hashGroupsChanged = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -0700503 }
sangho20eff1d2015-04-13 15:15:58 -0700504 } else {
Saurav Dasc88d4662017-05-15 15:34:25 -0700505 // link has gone down
506 // Compare existing ECMP SPG only with the link that went down
507 routeChanges = computeDamagedRoutes(linkDown);
508 if (routeChanges != null) {
509 processHashGroupChange(routeChanges, true, null);
510 // clear out routesChanges so a re-route is not attempted
511 routeChanges = ImmutableSet.of();
Saurav Dasc568c342018-01-25 09:49:01 -0800512 hashGroupsChanged = true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700513 }
sangho20eff1d2015-04-13 15:15:58 -0700514 }
515
Saurav Das4e3224f2016-11-29 14:27:25 -0800516 // do full re-routing if optimized routing returns null routeChanges
Saurav Dasb5c236e2016-06-07 10:08:06 -0700517 if (routeChanges == null) {
Saurav Dasc568c342018-01-25 09:49:01 -0800518 log.warn("Optimized routing failed... opting for full reroute");
Saurav Das7bcbe702017-06-13 15:35:54 -0700519 populationStatus = Status.ABORTED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700520 populateAllRoutingRules();
521 return;
Saurav Dasb5c236e2016-06-07 10:08:06 -0700522 }
523
sangho20eff1d2015-04-13 15:15:58 -0700524 if (routeChanges.isEmpty()) {
Saurav Dasc568c342018-01-25 09:49:01 -0800525 if (hashGroupsChanged) {
526 log.info("Hash-groups changed for link status change");
527 } else {
528 log.info("No re-route or re-hash attempted for the link"
529 + " status change");
530 updatedEcmpSpgMap.keySet().forEach(devId -> {
531 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
532 log.debug("Updating ECMPspg for remaining dev:{}", devId);
533 });
534 }
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700535 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700536 populationStatus = Status.SUCCEEDED;
Saurav Dasc88d4662017-05-15 15:34:25 -0700537 return;
sangho20eff1d2015-04-13 15:15:58 -0700538 }
539
Saurav Dasc88d4662017-05-15 15:34:25 -0700540 // reroute of routeChanges
Saurav Das7bcbe702017-06-13 15:35:54 -0700541 if (redoRouting(routeChanges, edgePairs, null)) {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700542 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED");
sangho20eff1d2015-04-13 15:15:58 -0700543 populationStatus = Status.SUCCEEDED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700544 log.info("Completed repopulation of rules for link-status change."
545 + " # of rules populated : {}", rulePopulator.getCounter());
Saurav Dasc88d4662017-05-15 15:34:25 -0700546 return;
sangho20eff1d2015-04-13 15:15:58 -0700547 } else {
Srikanth Vavilapalli23181912015-05-04 09:48:09 -0700548 log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED");
sangho20eff1d2015-04-13 15:15:58 -0700549 populationStatus = Status.ABORTED;
Saurav Das7bcbe702017-06-13 15:35:54 -0700550 log.warn("Failed to repopulate the rules for link status change.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700551 return;
sangho20eff1d2015-04-13 15:15:58 -0700552 }
HIGUCHI Yuta84a25fc2015-09-08 16:16:31 +0900553 } finally {
554 statusLock.unlock();
sangho20eff1d2015-04-13 15:15:58 -0700555 }
556 }
557
Saurav Dasc88d4662017-05-15 15:34:25 -0700558 /**
Saurav Das7bcbe702017-06-13 15:35:54 -0700559 * Processes a set a route-path changes by reprogramming routing rules and
560 * creating new hash-groups or editing them if necessary. This method also
561 * determines the next-hops for the route-path from the src-switch (target)
562 * of the path towards the dst-switch of the path.
Saurav Dasc88d4662017-05-15 15:34:25 -0700563 *
Saurav Das7bcbe702017-06-13 15:35:54 -0700564 * @param routeChanges a set of route-path changes, where each route-path is
565 * a list with its first element the src-switch (target)
566 * of the path, and the second element the dst-switch of
567 * the path.
568 * @param edgePairs a set of edge-switches that are paired by configuration
569 * @param subnets a set of prefixes that need to be populated in the routing
570 * table of the target switch in the route-path. Can be null,
571 * in which case all the prefixes belonging to the dst-switch
572 * will be populated in the target switch
573 * @return true if successful in repopulating all routes
Saurav Dasc88d4662017-05-15 15:34:25 -0700574 */
Saurav Das7bcbe702017-06-13 15:35:54 -0700575 private boolean redoRouting(Set<ArrayList<DeviceId>> routeChanges,
576 Set<EdgePair> edgePairs, Set<IpPrefix> subnets) {
577 // first make every entry two-elements
578 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
579 for (ArrayList<DeviceId> route : routeChanges) {
580 if (route.size() == 1) {
581 DeviceId dstSw = route.get(0);
582 EcmpShortestPathGraph ec = updatedEcmpSpgMap.get(dstSw);
583 if (ec == null) {
584 log.warn("No graph found for {} .. aborting redoRouting", dstSw);
585 return false;
586 }
587 ec.getAllLearnedSwitchesAndVia().keySet().forEach(key -> {
588 ec.getAllLearnedSwitchesAndVia().get(key).keySet().forEach(target -> {
589 changedRoutes.add(Lists.newArrayList(target, dstSw));
590 });
591 });
592 } else {
593 DeviceId targetSw = route.get(0);
594 DeviceId dstSw = route.get(1);
595 changedRoutes.add(Lists.newArrayList(targetSw, dstSw));
596 }
597 }
598
599 // now process changedRoutes according to edgePairs
600 if (!redoRoutingEdgePairs(edgePairs, subnets, changedRoutes)) {
601 return false; //abort routing and fail fast
602 }
603
604 // whatever is left in changedRoutes is now processed for individual dsts.
Saurav Dasc568c342018-01-25 09:49:01 -0800605 Set<DeviceId> updatedDevices = Sets.newHashSet();
606 if (!redoRoutingIndividualDests(subnets, changedRoutes,
607 updatedDevices)) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700608 return false; //abort routing and fail fast
609 }
610
Saurav Das7bcbe702017-06-13 15:35:54 -0700611 // update ecmpSPG for all edge-pairs
612 for (EdgePair ep : edgePairs) {
613 currentEcmpSpgMap.put(ep.dev1, updatedEcmpSpgMap.get(ep.dev1));
614 currentEcmpSpgMap.put(ep.dev2, updatedEcmpSpgMap.get(ep.dev2));
615 log.debug("Updating ECMPspg for edge-pair:{}-{}", ep.dev1, ep.dev2);
616 }
Saurav Dasc568c342018-01-25 09:49:01 -0800617
618 // here is where we update all devices not touched by this instance
619 updatedEcmpSpgMap.keySet().stream()
620 .filter(devId -> !edgePairs.stream().anyMatch(ep -> ep.includes(devId)))
621 .filter(devId -> !updatedDevices.contains(devId))
622 .forEach(devId -> {
623 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
624 log.debug("Updating ECMPspg for remaining dev:{}", devId);
625 });
Saurav Das7bcbe702017-06-13 15:35:54 -0700626 return true;
627 }
628
629 /**
630 * Programs targetSw in the changedRoutes for given prefixes reachable by
631 * an edgePair. If no prefixes are given, the method will use configured
632 * subnets/prefixes. If some configured subnets belong only to a specific
633 * destination in the edgePair, then the target switch will be programmed
634 * only to that destination.
635 *
636 * @param edgePairs set of edge-pairs for which target will be programmed
637 * @param subnets a set of prefixes that need to be populated in the routing
638 * table of the target switch in the changedRoutes. Can be null,
639 * in which case all the configured prefixes belonging to the
640 * paired switches will be populated in the target switch
641 * @param changedRoutes a set of route-path changes, where each route-path is
642 * a list with its first element the src-switch (target)
643 * of the path, and the second element the dst-switch of
644 * the path.
645 * @return true if successful
646 */
647 private boolean redoRoutingEdgePairs(Set<EdgePair> edgePairs,
648 Set<IpPrefix> subnets,
649 Set<ArrayList<DeviceId>> changedRoutes) {
650 for (EdgePair ep : edgePairs) {
651 // temp store for a target's changedRoutes to this edge-pair
652 Map<DeviceId, Set<ArrayList<DeviceId>>> targetRoutes = new HashMap<>();
653 Iterator<ArrayList<DeviceId>> i = changedRoutes.iterator();
654 while (i.hasNext()) {
655 ArrayList<DeviceId> route = i.next();
656 DeviceId dstSw = route.get(1);
657 if (ep.includes(dstSw)) {
658 // routeChange for edge pair found
659 // sort by target iff target is edge and remove from changedRoutes
660 DeviceId targetSw = route.get(0);
661 try {
662 if (!srManager.deviceConfiguration.isEdgeDevice(targetSw)) {
663 continue;
664 }
665 } catch (DeviceConfigNotFoundException e) {
666 log.warn(e.getMessage() + "aborting redoRouting");
667 return false;
668 }
669 // route is from another edge to this edge-pair
670 if (targetRoutes.containsKey(targetSw)) {
671 targetRoutes.get(targetSw).add(route);
672 } else {
673 Set<ArrayList<DeviceId>> temp = new HashSet<>();
674 temp.add(route);
675 targetRoutes.put(targetSw, temp);
676 }
677 i.remove();
678 }
679 }
680 // so now for this edgepair we have a per target set of routechanges
681 // process target->edgePair route
682 for (Map.Entry<DeviceId, Set<ArrayList<DeviceId>>> entry :
683 targetRoutes.entrySet()) {
684 log.debug("* redoRoutingDstPair Target:{} -> edge-pair {}",
685 entry.getKey(), ep);
686 DeviceId targetSw = entry.getKey();
687 Map<DeviceId, Set<DeviceId>> perDstNextHops = new HashMap<>();
688 entry.getValue().forEach(route -> {
689 Set<DeviceId> nhops = getNextHops(route.get(0), route.get(1));
690 log.debug("route: target {} -> dst {} found with next-hops {}",
691 route.get(0), route.get(1), nhops);
692 perDstNextHops.put(route.get(1), nhops);
693 });
694 Set<IpPrefix> ipDev1 = (subnets == null) ? config.getSubnets(ep.dev1)
695 : subnets;
696 Set<IpPrefix> ipDev2 = (subnets == null) ? config.getSubnets(ep.dev2)
697 : subnets;
698 ipDev1 = (ipDev1 == null) ? Sets.newHashSet() : ipDev1;
699 ipDev2 = (ipDev2 == null) ? Sets.newHashSet() : ipDev2;
Saurav Dasc568c342018-01-25 09:49:01 -0800700 Set<DeviceId> nhDev1 = perDstNextHops.get(ep.dev1);
701 Set<DeviceId> nhDev2 = perDstNextHops.get(ep.dev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700702 // handle routing to subnets common to edge-pair
Saurav Dasc568c342018-01-25 09:49:01 -0800703 // only if the targetSw is not part of the edge-pair and there
704 // exists a next hop to at least one of the devices in the edge-pair
705 if (!ep.includes(targetSw)
706 && ((nhDev1 != null && !nhDev1.isEmpty())
707 || (nhDev2 != null && !nhDev2.isEmpty()))) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700708 if (!populateEcmpRoutingRulePartial(
709 targetSw,
710 ep.dev1, ep.dev2,
711 perDstNextHops,
712 Sets.intersection(ipDev1, ipDev2))) {
713 return false; // abort everything and fail fast
714 }
715 }
Saurav Dasc568c342018-01-25 09:49:01 -0800716 // handle routing to subnets that only belong to dev1 only if
717 // a next-hop exists from the target to dev1
Saurav Das7bcbe702017-06-13 15:35:54 -0700718 Set<IpPrefix> onlyDev1Subnets = Sets.difference(ipDev1, ipDev2);
Saurav Dasc568c342018-01-25 09:49:01 -0800719 if (!onlyDev1Subnets.isEmpty()
720 && nhDev1 != null && !nhDev1.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700721 Map<DeviceId, Set<DeviceId>> onlyDev1NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800722 onlyDev1NextHops.put(ep.dev1, nhDev1);
Saurav Das7bcbe702017-06-13 15:35:54 -0700723 if (!populateEcmpRoutingRulePartial(
724 targetSw,
725 ep.dev1, null,
726 onlyDev1NextHops,
727 onlyDev1Subnets)) {
728 return false; // abort everything and fail fast
729 }
730 }
Saurav Dasc568c342018-01-25 09:49:01 -0800731 // handle routing to subnets that only belong to dev2 only if
732 // a next-hop exists from the target to dev2
Saurav Das7bcbe702017-06-13 15:35:54 -0700733 Set<IpPrefix> onlyDev2Subnets = Sets.difference(ipDev2, ipDev1);
Saurav Dasc568c342018-01-25 09:49:01 -0800734 if (!onlyDev2Subnets.isEmpty()
735 && nhDev2 != null && !nhDev2.isEmpty()) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700736 Map<DeviceId, Set<DeviceId>> onlyDev2NextHops = new HashMap<>();
Saurav Dasc568c342018-01-25 09:49:01 -0800737 onlyDev2NextHops.put(ep.dev2, nhDev2);
Saurav Das7bcbe702017-06-13 15:35:54 -0700738 if (!populateEcmpRoutingRulePartial(
739 targetSw,
740 ep.dev2, null,
741 onlyDev2NextHops,
742 onlyDev2Subnets)) {
743 return false; // abort everything and fail fast
744 }
745 }
746 }
747 // if it gets here it has succeeded for all targets to this edge-pair
748 }
749 return true;
750 }
751
752 /**
753 * Programs targetSw in the changedRoutes for given prefixes reachable by
754 * a destination switch that is not part of an edge-pair.
755 * If no prefixes are given, the method will use configured subnets/prefixes.
756 *
757 * @param subnets a set of prefixes that need to be populated in the routing
758 * table of the target switch in the changedRoutes. Can be null,
759 * in which case all the configured prefixes belonging to the
760 * paired switches will be populated in the target switch
761 * @param changedRoutes a set of route-path changes, where each route-path is
762 * a list with its first element the src-switch (target)
763 * of the path, and the second element the dst-switch of
764 * the path.
765 * @return true if successful
766 */
767 private boolean redoRoutingIndividualDests(Set<IpPrefix> subnets,
Saurav Dasc568c342018-01-25 09:49:01 -0800768 Set<ArrayList<DeviceId>> changedRoutes,
769 Set<DeviceId> updatedDevices) {
Saurav Das7bcbe702017-06-13 15:35:54 -0700770 // aggregate route-path changes for each dst device
771 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice =
772 new HashMap<>();
773 for (ArrayList<DeviceId> route: changedRoutes) {
774 DeviceId dstSw = route.get(1);
775 ArrayList<ArrayList<DeviceId>> deviceRoutes =
776 routesBydevice.get(dstSw);
777 if (deviceRoutes == null) {
778 deviceRoutes = new ArrayList<>();
779 routesBydevice.put(dstSw, deviceRoutes);
780 }
781 deviceRoutes.add(route);
782 }
783 for (DeviceId impactedDstDevice : routesBydevice.keySet()) {
784 ArrayList<ArrayList<DeviceId>> deviceRoutes =
785 routesBydevice.get(impactedDstDevice);
786 for (ArrayList<DeviceId> route: deviceRoutes) {
787 log.debug("* redoRoutingIndiDst Target: {} -> dst: {}",
788 route.get(0), route.get(1));
789 DeviceId targetSw = route.get(0);
790 DeviceId dstSw = route.get(1); // same as impactedDstDevice
791 Set<DeviceId> nextHops = getNextHops(targetSw, dstSw);
Saurav Dasbd071d82018-01-09 17:38:44 -0800792 if (nextHops.isEmpty()) {
793 log.warn("Could not find next hop from target:{} --> dst {} "
794 + "skipping this route", targetSw, dstSw);
795 continue;
796 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700797 Map<DeviceId, Set<DeviceId>> nhops = new HashMap<>();
798 nhops.put(dstSw, nextHops);
799 if (!populateEcmpRoutingRulePartial(targetSw, dstSw, null, nhops,
800 (subnets == null) ? Sets.newHashSet() : subnets)) {
801 return false; // abort routing and fail fast
802 }
803 log.debug("Populating flow rules from target: {} to dst: {}"
804 + " is successful", targetSw, dstSw);
805 }
806 //Only if all the flows for all impacted routes to a
807 //specific target are pushed successfully, update the
808 //ECMP graph for that target. Or else the next event
809 //would not see any changes in the ECMP graphs.
810 //In another case, the target switch has gone away, so
811 //routes can't be installed. In that case, the current map
812 //is updated here, without any flows being pushed.
813 currentEcmpSpgMap.put(impactedDstDevice,
814 updatedEcmpSpgMap.get(impactedDstDevice));
Saurav Dasc568c342018-01-25 09:49:01 -0800815 updatedDevices.add(impactedDstDevice);
Saurav Das7bcbe702017-06-13 15:35:54 -0700816 log.debug("Updating ECMPspg for impacted dev:{}", impactedDstDevice);
817 }
818 return true;
819 }
820
821 /**
822 * Populate ECMP rules for subnets from target to destination via nexthops.
823 *
824 * @param targetSw Device ID of target switch in which rules will be programmed
825 * @param destSw1 Device ID of final destination switch to which the rules will forward
826 * @param destSw2 Device ID of paired destination switch to which the rules will forward
827 * A null deviceId indicates packets should only be sent to destSw1
Saurav Dasa4020382018-02-14 14:14:54 -0800828 * @param nextHops Map of a set of next hops per destSw
Saurav Das7bcbe702017-06-13 15:35:54 -0700829 * @param subnets Subnets to be populated. If empty, populate all configured subnets.
830 * @return true if it succeeds in populating rules
831 */ // refactor
832 private boolean populateEcmpRoutingRulePartial(DeviceId targetSw,
833 DeviceId destSw1,
834 DeviceId destSw2,
835 Map<DeviceId, Set<DeviceId>> nextHops,
836 Set<IpPrefix> subnets) {
837 boolean result;
838 // If both target switch and dest switch are edge routers, then set IP
839 // rule for both subnet and router IP.
840 boolean targetIsEdge;
841 boolean dest1IsEdge;
842 Ip4Address dest1RouterIpv4, dest2RouterIpv4 = null;
843 Ip6Address dest1RouterIpv6, dest2RouterIpv6 = null;
844
845 try {
846 targetIsEdge = config.isEdgeDevice(targetSw);
847 dest1IsEdge = config.isEdgeDevice(destSw1);
848 dest1RouterIpv4 = config.getRouterIpv4(destSw1);
849 dest1RouterIpv6 = config.getRouterIpv6(destSw1);
850 if (destSw2 != null) {
851 dest2RouterIpv4 = config.getRouterIpv4(destSw2);
852 dest2RouterIpv6 = config.getRouterIpv6(destSw2);
853 }
854 } catch (DeviceConfigNotFoundException e) {
855 log.warn(e.getMessage() + " Aborting populateEcmpRoutingRulePartial.");
Saurav Dasc88d4662017-05-15 15:34:25 -0700856 return false;
857 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700858
859 if (targetIsEdge && dest1IsEdge) {
860 subnets = (subnets != null && !subnets.isEmpty())
861 ? Sets.newHashSet(subnets)
862 : Sets.newHashSet(config.getSubnets(destSw1));
Saurav Dasa4020382018-02-14 14:14:54 -0800863 // XXX - Rethink this - ignoring routerIPs in all other switches
864 // even edge to edge switches
Saurav Das7bcbe702017-06-13 15:35:54 -0700865 /*subnets.add(dest1RouterIpv4.toIpPrefix());
866 if (dest1RouterIpv6 != null) {
867 subnets.add(dest1RouterIpv6.toIpPrefix());
868 }
869 if (destSw2 != null && dest2RouterIpv4 != null) {
870 subnets.add(dest2RouterIpv4.toIpPrefix());
871 if (dest2RouterIpv6 != null) {
872 subnets.add(dest2RouterIpv6.toIpPrefix());
873 }
874 }*/
875 log.debug(". populateEcmpRoutingRulePartial in device {} towards {} {} "
876 + "for subnets {}", targetSw, destSw1,
877 (destSw2 != null) ? ("& " + destSw2) : "",
878 subnets);
879 result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets,
880 destSw1, destSw2,
881 nextHops);
882 if (!result) {
883 return false;
884 }
Saurav Dasc88d4662017-05-15 15:34:25 -0700885 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700886
887 if (!targetIsEdge && dest1IsEdge) {
888 // MPLS rules in all non-edge target devices. These rules are for
889 // individual destinations, even if the dsts are part of edge-pairs.
890 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
891 + "all MPLS rules", targetSw, destSw1);
892 result = rulePopulator.populateMplsRule(targetSw, destSw1,
893 nextHops.get(destSw1),
894 dest1RouterIpv4);
895 if (!result) {
896 return false;
897 }
898 if (dest1RouterIpv6 != null) {
Saurav Dasa4020382018-02-14 14:14:54 -0800899 int v4sid = 0, v6sid = 0;
900 try {
901 v4sid = config.getIPv4SegmentId(destSw1);
902 v6sid = config.getIPv6SegmentId(destSw1);
903 } catch (DeviceConfigNotFoundException e) {
904 log.warn(e.getMessage());
905 }
906 if (v4sid != v6sid) {
907 result = rulePopulator.populateMplsRule(targetSw, destSw1,
908 nextHops.get(destSw1),
909 dest1RouterIpv6);
910 if (!result) {
911 return false;
912 }
Saurav Das7bcbe702017-06-13 15:35:54 -0700913 }
914 }
915 }
916
Andreas Pantelopoulosff691b72018-03-12 16:30:20 -0700917 if (!targetIsEdge && !dest1IsEdge) {
918 // MPLS rules for inter-connected spines
919 // can be merged with above if, left it here for clarity
920 log.debug(". populateEcmpRoutingRulePartial in device{} towards {} for "
921 + "all MPLS rules", targetSw, destSw1);
922
923 result = rulePopulator.populateMplsRule(targetSw, destSw1,
924 nextHops.get(destSw1),
925 dest1RouterIpv4);
926 if (!result) {
927 return false;
928 }
929
930 if (dest1RouterIpv6 != null) {
931 int v4sid = 0, v6sid = 0;
932 try {
933 v4sid = config.getIPv4SegmentId(destSw1);
934 v6sid = config.getIPv6SegmentId(destSw1);
935 } catch (DeviceConfigNotFoundException e) {
936 log.warn(e.getMessage());
937 }
938 if (v4sid != v6sid) {
939 result = rulePopulator.populateMplsRule(targetSw, destSw1,
940 nextHops.get(destSw1),
941 dest1RouterIpv6);
942 if (!result) {
943 return false;
944 }
945 }
946 }
947 }
948
949
Saurav Das7bcbe702017-06-13 15:35:54 -0700950 // To save on ECMP groups
951 // avoid MPLS rules in non-edge-devices to non-edge-devices
952 // avoid MPLS transit rules in edge-devices
953 // avoid loopback IP rules in edge-devices to non-edge-devices
954 return true;
Saurav Dasc88d4662017-05-15 15:34:25 -0700955 }
956
957 /**
958 * Processes a set a route-path changes by editing hash groups.
959 *
960 * @param routeChanges a set of route-path changes, where each route-path is
961 * a list with its first element the src-switch of the path
962 * and the second element the dst-switch of the path.
963 * @param linkOrSwitchFailed true if the route changes are for a failed
964 * switch or linkDown event
965 * @param failedSwitch the switchId if the route changes are for a failed switch,
966 * otherwise null
967 */
968 private void processHashGroupChange(Set<ArrayList<DeviceId>> routeChanges,
969 boolean linkOrSwitchFailed,
970 DeviceId failedSwitch) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700971 Set<ArrayList<DeviceId>> changedRoutes = new HashSet<>();
972 // first, ensure each routeChanges entry has two elements
Saurav Dasc88d4662017-05-15 15:34:25 -0700973 for (ArrayList<DeviceId> route : routeChanges) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700974 if (route.size() == 1) {
975 // route-path changes are from everyone else to this switch
976 DeviceId dstSw = route.get(0);
977 srManager.deviceService.getAvailableDevices().forEach(sw -> {
978 if (!sw.id().equals(dstSw)) {
979 changedRoutes.add(Lists.newArrayList(sw.id(), dstSw));
980 }
981 });
982 } else {
983 changedRoutes.add(route);
Saurav Dasc88d4662017-05-15 15:34:25 -0700984 }
Saurav Das9df5b7c2017-08-14 16:44:43 -0700985 }
Saurav Dasc568c342018-01-25 09:49:01 -0800986 boolean someFailed = false;
987 Set<DeviceId> updatedDevices = Sets.newHashSet();
Saurav Das9df5b7c2017-08-14 16:44:43 -0700988 for (ArrayList<DeviceId> route : changedRoutes) {
989 DeviceId targetSw = route.get(0);
990 DeviceId dstSw = route.get(1);
Saurav Dasc88d4662017-05-15 15:34:25 -0700991 if (linkOrSwitchFailed) {
Saurav Das9df5b7c2017-08-14 16:44:43 -0700992 boolean success = fixHashGroupsForRoute(route, true);
Saurav Dasc88d4662017-05-15 15:34:25 -0700993 // it's possible that we cannot fix hash groups for a route
994 // if the target switch has failed. Nevertheless the ecmp graph
995 // for the impacted switch must still be updated.
Saurav Das9df5b7c2017-08-14 16:44:43 -0700996 if (!success && failedSwitch != null && targetSw.equals(failedSwitch)) {
Saurav Dasc88d4662017-05-15 15:34:25 -0700997 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
998 currentEcmpSpgMap.remove(targetSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -0700999 log.debug("Updating ECMPspg for dst:{} removing failed switch "
Saurav Dasc88d4662017-05-15 15:34:25 -07001000 + "target:{}", dstSw, targetSw);
Saurav Dasc568c342018-01-25 09:49:01 -08001001 updatedDevices.add(targetSw);
1002 updatedDevices.add(dstSw);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001003 continue;
Saurav Dasc88d4662017-05-15 15:34:25 -07001004 }
1005 //linkfailed - update both sides
Saurav Dasc88d4662017-05-15 15:34:25 -07001006 if (success) {
1007 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
Saurav Das9df5b7c2017-08-14 16:44:43 -07001008 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
Saurav Dasc568c342018-01-25 09:49:01 -08001009 log.debug("Updating ECMPspg for dst:{} and target:{} for linkdown"
1010 + " or switchdown", dstSw, targetSw);
1011 updatedDevices.add(targetSw);
1012 updatedDevices.add(dstSw);
1013 } else {
1014 someFailed = true;
Saurav Das9df5b7c2017-08-14 16:44:43 -07001015 }
1016 } else {
1017 //linkup of seen before link
1018 boolean success = fixHashGroupsForRoute(route, false);
1019 if (success) {
1020 currentEcmpSpgMap.put(targetSw, updatedEcmpSpgMap.get(targetSw));
1021 currentEcmpSpgMap.put(dstSw, updatedEcmpSpgMap.get(dstSw));
1022 log.debug("Updating ECMPspg for target:{} and dst:{} for linkup",
Saurav Dasc88d4662017-05-15 15:34:25 -07001023 targetSw, dstSw);
Saurav Dasc568c342018-01-25 09:49:01 -08001024 updatedDevices.add(targetSw);
1025 updatedDevices.add(dstSw);
1026 } else {
1027 someFailed = true;
Saurav Dasc88d4662017-05-15 15:34:25 -07001028 }
1029 }
1030 }
Saurav Dasc568c342018-01-25 09:49:01 -08001031 if (!someFailed) {
1032 // here is where we update all devices not touched by this instance
1033 updatedEcmpSpgMap.keySet().stream()
1034 .filter(devId -> !updatedDevices.contains(devId))
1035 .forEach(devId -> {
1036 currentEcmpSpgMap.put(devId, updatedEcmpSpgMap.get(devId));
1037 log.debug("Updating ECMPspg for remaining dev:{}", devId);
1038 });
1039 }
Saurav Dasc88d4662017-05-15 15:34:25 -07001040 }
1041
1042 /**
1043 * Edits hash groups in the src-switch (targetSw) of a route-path by
1044 * calling the groupHandler to either add or remove buckets in an existing
1045 * hash group.
1046 *
1047 * @param route a single list representing a route-path where the first element
1048 * is the src-switch (targetSw) of the route-path and the
1049 * second element is the dst-switch
1050 * @param revoke true if buckets in the hash-groups need to be removed;
1051 * false if buckets in the hash-groups need to be added
1052 * @return true if the hash group editing is successful
1053 */
1054 private boolean fixHashGroupsForRoute(ArrayList<DeviceId> route,
1055 boolean revoke) {
1056 DeviceId targetSw = route.get(0);
1057 if (route.size() < 2) {
1058 log.warn("Cannot fixHashGroupsForRoute - no dstSw in route {}", route);
1059 return false;
1060 }
1061 DeviceId destSw = route.get(1);
Saurav Das9df5b7c2017-08-14 16:44:43 -07001062 log.debug("* processing fixHashGroupsForRoute: Target {} -> Dest {}",
Saurav Dasc88d4662017-05-15 15:34:25 -07001063 targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001064 // figure out the new next hops at the targetSw towards the destSw
Saurav Das9df5b7c2017-08-14 16:44:43 -07001065 Set<DeviceId> nextHops = getNextHops(targetSw, destSw);
Saurav Dasc88d4662017-05-15 15:34:25 -07001066 // call group handler to change hash group at targetSw
1067 DefaultGroupHandler grpHandler = srManager.getGroupHandler(targetSw);
1068 if (grpHandler == null) {
1069 log.warn("Cannot find grouphandler for dev:{} .. aborting"
1070 + " {} hash group buckets for route:{} ", targetSw,
1071 (revoke) ? "revoke" : "repopulate", route);
1072 return false;
1073 }
1074 log.debug("{} hash-groups buckets For Route {} -> {} to next-hops {}",
1075 (revoke) ? "revoke" : "repopulating",
1076 targetSw, destSw, nextHops);
1077 return (revoke) ? grpHandler.fixHashGroups(targetSw, nextHops,
1078 destSw, true)
1079 : grpHandler.fixHashGroups(targetSw, nextHops,
1080 destSw, false);
1081 }
1082
1083 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001084 * Start the flow rule population process if it was never started. The
1085 * process finishes successfully when all flow rules are set and stops with
1086 * ABORTED status when any groups required for flows is not set yet.
Saurav Dasc88d4662017-05-15 15:34:25 -07001087 */
Saurav Das7bcbe702017-06-13 15:35:54 -07001088 public void startPopulationProcess() {
1089 statusLock.lock();
1090 try {
1091 if (populationStatus == Status.IDLE
1092 || populationStatus == Status.SUCCEEDED
1093 || populationStatus == Status.ABORTED) {
1094 populateAllRoutingRules();
sangho45b009c2015-05-07 13:30:57 -07001095 } else {
Saurav Das7bcbe702017-06-13 15:35:54 -07001096 log.warn("Not initiating startPopulationProcess as populationStatus is {}",
1097 populationStatus);
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001098 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001099 } finally {
1100 statusLock.unlock();
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001101 }
sangho20eff1d2015-04-13 15:15:58 -07001102 }
1103
Saurav Dasb5c236e2016-06-07 10:08:06 -07001104 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001105 * Revoke rules of given subnet in all edge switches.
1106 *
1107 * @param subnets subnet being removed
1108 * @return true if succeed
1109 */
1110 protected boolean revokeSubnet(Set<IpPrefix> subnets) {
1111 statusLock.lock();
1112 try {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001113 return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
1114 .map(Device::id)
1115 .filter(this::shouldProgram)
1116 .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
Saurav Das7bcbe702017-06-13 15:35:54 -07001117 } finally {
1118 statusLock.unlock();
1119 }
1120 }
1121
1122 /**
Charles Chan2fde6d42017-08-23 14:46:43 -07001123 * Populates IP rules for a route that has direct connection to the switch
1124 * if the current instance is the master of the switch.
1125 *
1126 * @param deviceId device ID of the device that next hop attaches to
1127 * @param prefix IP prefix of the route
1128 * @param hostMac MAC address of the next hop
1129 * @param hostVlanId Vlan ID of the nexthop
1130 * @param outPort port where the next hop attaches to
1131 */
1132 void populateRoute(DeviceId deviceId, IpPrefix prefix,
1133 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001134 if (shouldProgram(deviceId)) {
Charles Chan2fde6d42017-08-23 14:46:43 -07001135 srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1136 }
1137 }
1138
1139 /**
1140 * Removes IP rules for a route when the next hop is gone.
1141 * if the current instance is the master of the switch.
1142 *
1143 * @param deviceId device ID of the device that next hop attaches to
1144 * @param prefix IP prefix of the route
1145 * @param hostMac MAC address of the next hop
1146 * @param hostVlanId Vlan ID of the nexthop
1147 * @param outPort port that next hop attaches to
1148 */
1149 void revokeRoute(DeviceId deviceId, IpPrefix prefix,
1150 MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
Charles Chan2ff1bac2018-03-29 16:03:41 -07001151 if (shouldProgram(deviceId)) {
Charles Chan2fde6d42017-08-23 14:46:43 -07001152 srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
1153 }
1154 }
1155
Charles Chan2ff1bac2018-03-29 16:03:41 -07001156 void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1157 if (shouldProgram(deviceId)) {
1158 srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
1159 }
1160 }
1161
1162 void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
1163 if (shouldProgram(deviceId)) {
1164 srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
1165 }
1166 }
1167
1168 void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
1169 VlanId vlanId, boolean popVlan, boolean install) {
1170 if (shouldProgram(deviceId)) {
1171 srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
1172 }
1173 }
1174
1175 void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
1176 VlanId vlanId, boolean popVlan, boolean install) {
1177 if (shouldProgram(deviceId)) {
1178 srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
1179 vlanId, popVlan, install);
1180 }
1181 }
1182
Charles Chan2fde6d42017-08-23 14:46:43 -07001183 /**
Jonghwan Hyun800d9d02018-04-09 09:40:50 -07001184 * Populates IP rules for a route when the next hop is double-tagged.
1185 *
1186 * @param deviceId device ID that next hop attaches to
1187 * @param prefix IP prefix of the route
1188 * @param hostMac MAC address of the next hop
1189 * @param innerVlan Inner Vlan ID of the next hop
1190 * @param outerVlan Outer Vlan ID of the next hop
1191 * @param outerTpid Outer TPID of the next hop
1192 * @param outPort port that the next hop attaches to
1193 */
1194 void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1195 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1196 if (srManager.mastershipService.isLocalMaster(deviceId)) {
1197 VlanId dummyVlan = srManager.allocateDummyVlanId(
1198 new ConnectPoint(deviceId, outPort), prefix.address());
1199 if (!dummyVlan.equals(VlanId.NONE)) {
1200 srManager.routingRulePopulator.populateDoubleTaggedRoute(
1201 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1202 srManager.routingRulePopulator.processDoubleTaggedFilter(
1203 deviceId, outPort, outerVlan, innerVlan, true);
1204 } else {
1205 log.error("Failed to allocate dummy VLAN ID for host {} at {}/{}",
1206 prefix.address(), deviceId, outPort);
1207 }
1208 }
1209 }
1210
1211 /**
1212 * Revokes IP rules for a route when the next hop is double-tagged.
1213 *
1214 * @param deviceId device ID that next hop attaches to
1215 * @param prefix IP prefix of the route
1216 * @param hostMac MAC address of the next hop
1217 * @param innerVlan Inner Vlan ID of the next hop
1218 * @param outerVlan Outer Vlan ID of the next hop
1219 * @param outerTpid Outer TPID of the next hop
1220 * @param outPort port that the next hop attaches to
1221 */
1222 void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
1223 VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
1224 // Revoke route either if this node have the mastership (when device is available) or
1225 // if this node is the leader (even when device is unavailable)
1226 if (!srManager.mastershipService.isLocalMaster(deviceId)) {
1227 if (srManager.deviceService.isAvailable(deviceId)) {
1228 // Master node will revoke specified rule.
1229 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1230 return;
1231 }
1232
1233 // isLocalMaster will return false when the device is unavailable.
1234 // Verify if this node is the leader in that case.
1235 NodeId leader = srManager.leadershipService.runForLeadership(
1236 deviceId.toString()).leaderNodeId();
1237 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
1238 // Leader node will revoke specified rule.
1239 log.debug("This node is not a master for {}, stop revoking route.", deviceId);
1240 return;
1241 }
1242 }
1243
1244 VlanId dummyVlan = srManager.dummyVlanIdStore().get(new DummyVlanIdStoreKey(
1245 new ConnectPoint(deviceId, outPort), prefix.address()));
1246 if (dummyVlan == null) {
1247 log.error("Failed to get dummyVlanId for host {} at {}/{}.",
1248 prefix.address(), deviceId, outPort);
1249 } else {
1250 srManager.routingRulePopulator.revokeDoubleTaggedRoute(
1251 deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
1252 srManager.routingRulePopulator.processDoubleTaggedFilter(
1253 deviceId, outPort, outerVlan, innerVlan, false);
1254 }
1255 }
1256
1257
1258 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001259 * Remove ECMP graph entry for the given device. Typically called when
1260 * device is no longer available.
1261 *
1262 * @param deviceId the device for which graphs need to be purged
1263 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001264 void purgeEcmpGraph(DeviceId deviceId) {
Saurav Dasc568c342018-01-25 09:49:01 -08001265 statusLock.lock();
1266 try {
1267
1268 if (populationStatus == Status.STARTED) {
1269 log.warn("Previous rule population is not finished. Cannot"
1270 + " proceeed with purgeEcmpGraph for {}", deviceId);
1271 return;
1272 }
1273 log.debug("Updating ECMPspg for unavailable dev:{}", deviceId);
1274 currentEcmpSpgMap.remove(deviceId);
1275 if (updatedEcmpSpgMap != null) {
1276 updatedEcmpSpgMap.remove(deviceId);
1277 }
1278 } finally {
1279 statusLock.unlock();
Saurav Das7bcbe702017-06-13 15:35:54 -07001280 }
1281 }
1282
Saurav Das201762d2018-04-21 17:19:48 -07001283 /**
1284 * Attempts a full reroute of route-paths if topology has changed relatively
1285 * close to a mastership change event. Does not do a reroute if mastership
1286 * change is due to reasons other than a ONOS cluster event - for example a
1287 * call to balance-masters, or a switch up/down event.
1288 *
1289 * @param devId the device identifier for which mastership has changed
1290 * @param me the mastership event
1291 */
1292 void checkFullRerouteForMasterChange(DeviceId devId, MastershipEvent me) {
1293 // give small delay to absorb mastership events that are caused by
1294 // device that has disconnected from cluster
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001295 executorServiceMstChg.schedule(new MasterChange(devId, me),
1296 MASTER_CHANGE_DELAY, TimeUnit.MILLISECONDS);
Saurav Das201762d2018-04-21 17:19:48 -07001297 }
1298
1299 protected final class MasterChange implements Runnable {
1300 private DeviceId devId;
1301 private MastershipEvent me;
1302 private static final long CLUSTER_EVENT_THRESHOLD = 4500; // ms
1303 private static final long DEVICE_EVENT_THRESHOLD = 2000; // ms
1304
1305 MasterChange(DeviceId devId, MastershipEvent me) {
1306 this.devId = devId;
1307 this.me = me;
1308 }
1309
1310 @Override
1311 public void run() {
1312 long lce = srManager.clusterListener.timeSinceLastClusterEvent();
1313 boolean clusterEvent = lce < CLUSTER_EVENT_THRESHOLD;
1314
1315 // ignore event for lost switch if cluster event hasn't happened -
1316 // device down event will handle it
1317 if ((me.roleInfo().master() == null
1318 || !srManager.deviceService.isAvailable(devId))
1319 && !clusterEvent) {
1320 log.debug("Full reroute not required for lost device: {}/{} "
1321 + "clusterEvent/timeSince: {}/{}",
1322 devId, me.roleInfo(), clusterEvent, lce);
1323 return;
1324 }
1325
1326 long update = srManager.deviceService.getLastUpdatedInstant(devId);
1327 long lde = Instant.now().toEpochMilli() - update;
1328 boolean deviceEvent = lde < DEVICE_EVENT_THRESHOLD;
1329
1330 // ignore event for recently connected switch if cluster event hasn't
1331 // happened - link up events will handle it
1332 if (srManager.deviceService.isAvailable(devId) && deviceEvent
1333 && !clusterEvent) {
1334 log.debug("Full reroute not required for recently available"
1335 + " device: {}/{} deviceEvent/timeSince: {}/{} "
1336 + "clusterEvent/timeSince: {}/{}",
1337 devId, me.roleInfo(), deviceEvent, lde, clusterEvent, lce);
1338 return;
1339 }
1340
1341 // if it gets here, then mastership change is likely due to onos
1342 // instance failure, or network partition in onos cluster
1343 // normally a mastership change like this does not require re-programming
1344 // but if topology changes happen at the same time then we may miss events
1345 if (!isRoutingStable() && clusterEvent) {
1346 log.warn("Mastership changed for dev: {}/{} while programming "
1347 + "due to clusterEvent {} ms ago .. attempting full reroute",
1348 devId, me.roleInfo(), lce);
1349 if (srManager.mastershipService.isLocalMaster(devId)) {
1350 // old master could have died when populating filters
1351 populatePortAddressingRules(devId);
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001352 // old master could have died when creating groups
1353 srManager.purgeHashedNextObjectiveStore(devId);
Saurav Das201762d2018-04-21 17:19:48 -07001354 }
Saurav Das201762d2018-04-21 17:19:48 -07001355 // XXX right now we have no fine-grained way to only make changes
1356 // for the route paths affected by this device.
1357 populateAllRoutingRules();
1358 } else {
1359 log.debug("Stable route-paths .. full reroute not attempted for "
1360 + "mastership change {}/{} deviceEvent/timeSince: {}/{} "
1361 + "clusterEvent/timeSince: {}/{}", devId, me.roleInfo(),
1362 deviceEvent, lde, clusterEvent, lce);
1363 }
1364 }
1365 }
1366
Saurav Das7bcbe702017-06-13 15:35:54 -07001367 //////////////////////////////////////
1368 // Routing helper methods and classes
1369 //////////////////////////////////////
1370
1371 /**
Saurav Das4e3224f2016-11-29 14:27:25 -08001372 * Computes set of affected routes due to failed link. Assumes
Saurav Dasb5c236e2016-06-07 10:08:06 -07001373 * previous ecmp shortest-path graph exists for a switch in order to compute
1374 * affected routes. If such a graph does not exist, the method returns null.
1375 *
1376 * @param linkFail the failed link
1377 * @return the set of affected routes which may be empty if no routes were
1378 * affected, or null if no previous ecmp spg was found for comparison
1379 */
sangho20eff1d2015-04-13 15:15:58 -07001380 private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) {
sangho20eff1d2015-04-13 15:15:58 -07001381 Set<ArrayList<DeviceId>> routes = new HashSet<>();
1382
1383 for (Device sw : srManager.deviceService.getDevices()) {
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001384 log.debug("Computing the impacted routes for device {} due to link fail",
1385 sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001386 if (!shouldProgram(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001387 lastProgrammed.remove(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001388 continue;
1389 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001390 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001391 // check for mastership change since last run
1392 if (!lastProgrammed.contains(sw.id())) {
1393 lastProgrammed.add(sw.id());
1394 log.warn("New reponsibility for this node to program dev:{}"
1395 + " ... nuking current ECMPspg", sw.id());
1396 currentEcmpSpgMap.remove(sw.id());
1397 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001398 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
1399 if (ecmpSpg == null) {
1400 log.warn("No existing ECMP graph for switch {}. Aborting optimized"
1401 + " rerouting and opting for full-reroute", rootSw);
1402 return null;
1403 }
1404 if (log.isDebugEnabled()) {
1405 log.debug("Root switch: {}", rootSw);
1406 log.debug(" Current/Existing SPG: {}", ecmpSpg);
1407 log.debug(" New/Updated SPG: {}", updatedEcmpSpgMap.get(rootSw));
1408 }
1409 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>
1410 switchVia = ecmpSpg.getAllLearnedSwitchesAndVia();
1411 // figure out if the broken link affected any route-paths in this graph
1412 for (Integer itrIdx : switchVia.keySet()) {
1413 log.trace("Current/Exiting SPG Iterindex# {}", itrIdx);
1414 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1415 switchVia.get(itrIdx);
1416 for (DeviceId targetSw : swViaMap.keySet()) {
1417 log.trace("TargetSwitch {} --> RootSwitch {}",
1418 targetSw, rootSw);
Saurav Dasb5c236e2016-06-07 10:08:06 -07001419 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1420 log.trace(" Via:");
Pier Ventree0ae7a32016-11-23 09:57:42 -08001421 via.forEach(e -> log.trace(" {}", e));
Saurav Dasb5c236e2016-06-07 10:08:06 -07001422 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001423 Set<ArrayList<DeviceId>> subLinks =
1424 computeLinks(targetSw, rootSw, swViaMap);
1425 for (ArrayList<DeviceId> alink: subLinks) {
1426 if ((alink.get(0).equals(linkFail.src().deviceId()) &&
1427 alink.get(1).equals(linkFail.dst().deviceId()))
1428 ||
1429 (alink.get(0).equals(linkFail.dst().deviceId()) &&
1430 alink.get(1).equals(linkFail.src().deviceId()))) {
1431 log.debug("Impacted route:{}->{}", targetSw, rootSw);
1432 ArrayList<DeviceId> aRoute = new ArrayList<>();
1433 aRoute.add(targetSw); // switch with rules to populate
1434 aRoute.add(rootSw); // towards this destination
1435 routes.add(aRoute);
1436 break;
1437 }
sangho20eff1d2015-04-13 15:15:58 -07001438 }
1439 }
1440 }
Saurav Das9df5b7c2017-08-14 16:44:43 -07001441
sangho20eff1d2015-04-13 15:15:58 -07001442 }
sangho45b009c2015-05-07 13:30:57 -07001443
sangho20eff1d2015-04-13 15:15:58 -07001444 }
sangho20eff1d2015-04-13 15:15:58 -07001445 return routes;
1446 }
1447
Saurav Das4e3224f2016-11-29 14:27:25 -08001448 /**
1449 * Computes set of affected routes due to new links or failed switches.
1450 *
Saurav Das604ab3a2018-03-18 21:28:15 -07001451 * @param failedSwitch deviceId of failed switch if any
Saurav Das4e3224f2016-11-29 14:27:25 -08001452 * @return the set of affected routes which may be empty if no routes were
1453 * affected
1454 */
Saurav Dase0d4c872018-03-05 14:37:16 -08001455 private Set<ArrayList<DeviceId>> computeRouteChange(DeviceId failedSwitch) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001456 ImmutableSet.Builder<ArrayList<DeviceId>> changedRtBldr =
Saurav Das4e3224f2016-11-29 14:27:25 -08001457 ImmutableSet.builder();
sangho20eff1d2015-04-13 15:15:58 -07001458
1459 for (Device sw : srManager.deviceService.getDevices()) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001460 log.debug("Computing the impacted routes for device {}", sw.id());
Charles Chan2ff1bac2018-03-29 16:03:41 -07001461 if (!shouldProgram(sw.id())) {
Saurav Das201762d2018-04-21 17:19:48 -07001462 lastProgrammed.remove(sw.id());
sangho20eff1d2015-04-13 15:15:58 -07001463 continue;
1464 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001465 for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
Saurav Das7bcbe702017-06-13 15:35:54 -07001466 if (log.isTraceEnabled()) {
1467 log.trace("Device links for dev: {}", rootSw);
1468 for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
1469 log.trace("{} -> {} ", link.src().deviceId(),
1470 link.dst().deviceId());
1471 }
Saurav Dasb5c236e2016-06-07 10:08:06 -07001472 }
Saurav Das201762d2018-04-21 17:19:48 -07001473 // check for mastership change since last run
1474 if (!lastProgrammed.contains(sw.id())) {
1475 lastProgrammed.add(sw.id());
1476 log.warn("New reponsibility for this node to program dev:{}"
1477 + " ... nuking current ECMPspg", sw.id());
1478 currentEcmpSpgMap.remove(sw.id());
1479 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001480 EcmpShortestPathGraph currEcmpSpg = currentEcmpSpgMap.get(rootSw);
1481 if (currEcmpSpg == null) {
1482 log.debug("No existing ECMP graph for device {}.. adding self as "
1483 + "changed route", rootSw);
1484 changedRtBldr.add(Lists.newArrayList(rootSw));
1485 continue;
1486 }
1487 EcmpShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(rootSw);
Saurav Das5a356042018-04-06 20:16:01 -07001488 if (newEcmpSpg == null) {
1489 log.warn("Cannot find updated ECMP graph for dev:{}", rootSw);
1490 continue;
1491 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001492 if (log.isDebugEnabled()) {
1493 log.debug("Root switch: {}", rootSw);
1494 log.debug(" Current/Existing SPG: {}", currEcmpSpg);
1495 log.debug(" New/Updated SPG: {}", newEcmpSpg);
1496 }
1497 // first use the updated/new map to compare to current/existing map
1498 // as new links may have come up
1499 changedRtBldr.addAll(compareGraphs(newEcmpSpg, currEcmpSpg, rootSw));
1500 // then use the current/existing map to compare to updated/new map
1501 // as switch may have been removed
1502 changedRtBldr.addAll(compareGraphs(currEcmpSpg, newEcmpSpg, rootSw));
sangho45b009c2015-05-07 13:30:57 -07001503 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001504 }
sangho20eff1d2015-04-13 15:15:58 -07001505
Saurav Dase0d4c872018-03-05 14:37:16 -08001506 // handle clearing state for a failed switch in case the switch does
1507 // not have a pair, or the pair is not available
1508 if (failedSwitch != null) {
Charles Chanba6c5752018-04-02 11:46:38 -07001509 Optional<DeviceId> pairDev = srManager.getPairDeviceId(failedSwitch);
1510 if (!pairDev.isPresent() || !srManager.deviceService.isAvailable(pairDev.get())) {
Saurav Dase0d4c872018-03-05 14:37:16 -08001511 log.debug("Proxy Route changes to downed Sw:{}", failedSwitch);
1512 srManager.deviceService.getDevices().forEach(dev -> {
1513 if (!dev.id().equals(failedSwitch) &&
1514 srManager.mastershipService.isLocalMaster(dev.id())) {
1515 log.debug(" : {}", dev.id());
1516 changedRtBldr.add(Lists.newArrayList(dev.id(), failedSwitch));
1517 }
1518 });
1519 }
1520 }
1521
Saurav Das7bcbe702017-06-13 15:35:54 -07001522 Set<ArrayList<DeviceId>> changedRoutes = changedRtBldr.build();
Saurav Das4e3224f2016-11-29 14:27:25 -08001523 for (ArrayList<DeviceId> route: changedRoutes) {
1524 log.debug("Route changes Target -> Root");
1525 if (route.size() == 1) {
1526 log.debug(" : all -> {}", route.get(0));
1527 } else {
1528 log.debug(" : {} -> {}", route.get(0), route.get(1));
1529 }
1530 }
1531 return changedRoutes;
1532 }
1533
1534 /**
1535 * For the root switch, searches all the target nodes reachable in the base
1536 * graph, and compares paths to the ones in the comp graph.
1537 *
1538 * @param base the graph that is indexed for all reachable target nodes
1539 * from the root node
1540 * @param comp the graph that the base graph is compared to
1541 * @param rootSw both ecmp graphs are calculated for the root node
1542 * @return all the routes that have changed in the base graph
1543 */
1544 private Set<ArrayList<DeviceId>> compareGraphs(EcmpShortestPathGraph base,
1545 EcmpShortestPathGraph comp,
1546 DeviceId rootSw) {
1547 ImmutableSet.Builder<ArrayList<DeviceId>> changedRoutesBuilder =
1548 ImmutableSet.builder();
1549 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> baseMap =
1550 base.getAllLearnedSwitchesAndVia();
1551 HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> compMap =
1552 comp.getAllLearnedSwitchesAndVia();
1553 for (Integer itrIdx : baseMap.keySet()) {
1554 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> baseViaMap =
1555 baseMap.get(itrIdx);
1556 for (DeviceId targetSw : baseViaMap.keySet()) {
1557 ArrayList<ArrayList<DeviceId>> basePath = baseViaMap.get(targetSw);
1558 ArrayList<ArrayList<DeviceId>> compPath = getVia(compMap, targetSw);
1559 if ((compPath == null) || !basePath.equals(compPath)) {
Saurav Dasc88d4662017-05-15 15:34:25 -07001560 log.trace("Impacted route:{} -> {}", targetSw, rootSw);
Saurav Das4e3224f2016-11-29 14:27:25 -08001561 ArrayList<DeviceId> route = new ArrayList<>();
Saurav Das7bcbe702017-06-13 15:35:54 -07001562 route.add(targetSw); // switch with rules to populate
1563 route.add(rootSw); // towards this destination
Saurav Das4e3224f2016-11-29 14:27:25 -08001564 changedRoutesBuilder.add(route);
sangho20eff1d2015-04-13 15:15:58 -07001565 }
1566 }
sangho45b009c2015-05-07 13:30:57 -07001567 }
Saurav Das4e3224f2016-11-29 14:27:25 -08001568 return changedRoutesBuilder.build();
sangho20eff1d2015-04-13 15:15:58 -07001569 }
1570
Saurav Das7bcbe702017-06-13 15:35:54 -07001571 /**
1572 * Returns the ECMP paths traversed to reach the target switch.
1573 *
1574 * @param switchVia a per-iteration view of the ECMP graph for a root switch
1575 * @param targetSw the switch to reach from the root switch
1576 * @return the nodes traversed on ECMP paths to the target switch
1577 */
sangho20eff1d2015-04-13 15:15:58 -07001578 private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId,
Saurav Das4e3224f2016-11-29 14:27:25 -08001579 ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId targetSw) {
sangho20eff1d2015-04-13 15:15:58 -07001580 for (Integer itrIdx : switchVia.keySet()) {
1581 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1582 switchVia.get(itrIdx);
Saurav Das4e3224f2016-11-29 14:27:25 -08001583 if (swViaMap.get(targetSw) == null) {
sangho20eff1d2015-04-13 15:15:58 -07001584 continue;
1585 } else {
Saurav Das4e3224f2016-11-29 14:27:25 -08001586 return swViaMap.get(targetSw);
sangho20eff1d2015-04-13 15:15:58 -07001587 }
1588 }
1589
Srikanth Vavilapalli5428b6c2015-05-14 20:22:47 -07001590 return null;
sangho20eff1d2015-04-13 15:15:58 -07001591 }
1592
Saurav Das7bcbe702017-06-13 15:35:54 -07001593 /**
1594 * Utility method to break down a path from src to dst device into a collection
1595 * of links.
1596 *
1597 * @param src src device of the path
1598 * @param dst dst device of the path
1599 * @param viaMap path taken from src to dst device
1600 * @return collection of links in the path
1601 */
sangho20eff1d2015-04-13 15:15:58 -07001602 private Set<ArrayList<DeviceId>> computeLinks(DeviceId src,
1603 DeviceId dst,
1604 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) {
1605 Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet();
1606 for (ArrayList<DeviceId> via : viaMap.get(src)) {
1607 DeviceId linkSrc = src;
1608 DeviceId linkDst = dst;
1609 for (DeviceId viaDevice: via) {
1610 ArrayList<DeviceId> link = new ArrayList<>();
1611 linkDst = viaDevice;
1612 link.add(linkSrc);
1613 link.add(linkDst);
1614 subLinks.add(link);
1615 linkSrc = viaDevice;
1616 }
1617 ArrayList<DeviceId> link = new ArrayList<>();
1618 link.add(linkSrc);
1619 link.add(dst);
1620 subLinks.add(link);
1621 }
1622
1623 return subLinks;
1624 }
1625
Charles Chan93e71ba2016-04-29 14:38:22 -07001626 /**
Charles Chan2ff1bac2018-03-29 16:03:41 -07001627 * Determines whether this controller instance should program the
Saurav Das7bcbe702017-06-13 15:35:54 -07001628 * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
Charles Chan2ff1bac2018-03-29 16:03:41 -07001629 * <p>
1630 * Once an instance is elected, it will be the only instance responsible for programming
1631 * both devices in the pair until it goes down.
Charles Chan93e71ba2016-04-29 14:38:22 -07001632 *
Saurav Das7bcbe702017-06-13 15:35:54 -07001633 * @param deviceId device identifier to consider for routing
Charles Chan2ff1bac2018-03-29 16:03:41 -07001634 * @return true if current instance should handle the routing for given device
Charles Chan93e71ba2016-04-29 14:38:22 -07001635 */
Charles Chan2ff1bac2018-03-29 16:03:41 -07001636 boolean shouldProgram(DeviceId deviceId) {
Charles Chan50bb6ef2018-04-18 18:41:05 -07001637 Boolean cached = shouldProgramCache.get(deviceId);
1638 if (cached != null) {
Saurav Das201762d2018-04-21 17:19:48 -07001639 log.debug("shouldProgram dev:{} cached:{}", deviceId, cached);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001640 return cached;
1641 }
1642
Charles Chan2ff1bac2018-03-29 16:03:41 -07001643 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
sanghob35a6192015-04-01 13:05:26 -07001644
Charles Chan2ff1bac2018-03-29 16:03:41 -07001645 NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
1646 NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
1647 Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
1648 log.debug("Evaluate shouldProgram {}/pair={}. current={}, master={}, pairMaster={}",
1649 deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
1650
1651 // No pair device configured. Only handle when current instance is the master of the device
1652 if (!pairDeviceId.isPresent()) {
1653 log.debug("No pair device. current={}, master={}", currentNodeId, masterNodeId);
1654 return currentNodeId.equals(masterNodeId);
sanghob35a6192015-04-01 13:05:26 -07001655 }
Charles Chan2ff1bac2018-03-29 16:03:41 -07001656
1657 // Should not handle if current instance is not the master of either switch
1658 if (!currentNodeId.equals(masterNodeId) &&
1659 !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
1660 log.debug("Current node {} is neither the master of target device {} nor pair device {}",
1661 currentNodeId, deviceId, pairDeviceId);
1662 return false;
1663 }
1664
1665 Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
1666
1667 NodeId king = shouldProgram.compute(key, ((k, v) -> {
1668 if (v == null) {
1669 // There is no value in the map. Elect a node
1670 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1671 } else {
1672 if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
1673 // Use the node in the map if it is still alive and is a master of any of the two switches
1674 return v;
1675 } else {
1676 // Previously elected node is no longer the master of either switch. Re-elect a node.
1677 return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
1678 }
1679 }
1680 }));
1681
1682 if (king != null) {
1683 log.debug("{} should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001684 shouldProgramCache.put(deviceId, king.equals(currentNodeId));
Charles Chan2ff1bac2018-03-29 16:03:41 -07001685 return king.equals(currentNodeId);
1686 } else {
1687 log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
Charles Chan50bb6ef2018-04-18 18:41:05 -07001688 shouldProgramCache.remove(deviceId);
Charles Chan2ff1bac2018-03-29 16:03:41 -07001689 return false;
1690 }
1691 }
1692
1693 /**
1694 * Elects a node who should take responsibility of programming devices.
1695 * @param nodeIds list of candidate node ID
1696 *
1697 * @return NodeId of the node that gets elected, or null if none of the node can be elected
1698 */
1699 private NodeId elect(List<NodeId> nodeIds) {
1700 // Remove all null elements. This could happen when some device has no master
1701 nodeIds.removeAll(Collections.singleton(null));
1702 nodeIds.sort(null);
1703 return nodeIds.size() == 0 ? null : nodeIds.get(0);
1704 }
1705
Charles Chan50bb6ef2018-04-18 18:41:05 -07001706 void invalidateShouldProgramCache(DeviceId deviceId) {
1707 shouldProgramCache.remove(deviceId);
1708 }
1709
Charles Chan2ff1bac2018-03-29 16:03:41 -07001710 /**
1711 * Returns a set of device ID, containing given device and its pair device if exist.
1712 *
1713 * @param deviceId Device ID
1714 * @return a set of device ID, containing given device and its pair device if exist.
1715 */
1716 private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
1717 Set<DeviceId> ret = Sets.newHashSet(deviceId);
1718 srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
1719 return ret;
sanghob35a6192015-04-01 13:05:26 -07001720 }
1721
Charles Chan93e71ba2016-04-29 14:38:22 -07001722 /**
Saurav Das7bcbe702017-06-13 15:35:54 -07001723 * Returns the set of deviceIds which are the next hops from the targetSw
1724 * to the dstSw according to the latest ECMP spg.
1725 *
1726 * @param targetSw the switch for which the next-hops are desired
1727 * @param dstSw the switch to which the next-hops lead to from the targetSw
1728 * @return set of next hop deviceIds, could be empty if no next hops are found
1729 */
1730 private Set<DeviceId> getNextHops(DeviceId targetSw, DeviceId dstSw) {
1731 boolean targetIsEdge = false;
1732 try {
1733 targetIsEdge = srManager.deviceConfiguration.isEdgeDevice(targetSw);
1734 } catch (DeviceConfigNotFoundException e) {
1735 log.warn(e.getMessage() + "Cannot determine if targetIsEdge {}.. "
1736 + "continuing to getNextHops", targetSw);
1737 }
1738
1739 EcmpShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dstSw);
1740 if (ecmpSpg == null) {
1741 log.debug("No ecmpSpg found for dstSw: {}", dstSw);
1742 return ImmutableSet.of();
1743 }
1744 HashMap<Integer,
1745 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
1746 ecmpSpg.getAllLearnedSwitchesAndVia();
1747 for (Integer itrIdx : switchVia.keySet()) {
1748 HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
1749 switchVia.get(itrIdx);
1750 for (DeviceId target : swViaMap.keySet()) {
1751 if (!target.equals(targetSw)) {
1752 continue;
1753 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001754 // optimization for spines to not use leaves to get
1755 // to a spine or other leaves. Also leaves should not use other
1756 // leaves to get to the destination
1757 if ((!targetIsEdge && itrIdx > 1) || targetIsEdge) {
Saurav Dasa4020382018-02-14 14:14:54 -08001758 boolean pathdevIsEdge = false;
1759 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001760 log.debug("Evaluating next-hop in path: {}", via);
Saurav Dasa4020382018-02-14 14:14:54 -08001761 for (DeviceId pathdev : via) {
1762 try {
1763 pathdevIsEdge = srManager.deviceConfiguration
1764 .isEdgeDevice(pathdev);
1765 } catch (DeviceConfigNotFoundException e) {
1766 log.warn(e.getMessage());
1767 }
1768 if (pathdevIsEdge) {
1769 log.debug("Avoiding {} hop path for non-edge targetSw:{}"
1770 + " --> dstSw:{} which goes through an edge"
1771 + " device {} in path {}", itrIdx,
1772 targetSw, dstSw, pathdev, via);
1773 return ImmutableSet.of();
1774 }
1775 }
1776 }
Saurav Das7bcbe702017-06-13 15:35:54 -07001777 }
1778 Set<DeviceId> nextHops = new HashSet<>();
1779 for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
1780 if (via.isEmpty()) {
1781 // the dstSw is the next-hop from the targetSw
1782 nextHops.add(dstSw);
1783 } else {
1784 // first elem is next-hop in each ECMP path
1785 nextHops.add(via.get(0));
1786 }
1787 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001788 log.debug("target {} --> dst: {} has next-hops:{}", targetSw,
1789 dstSw, nextHops);
Saurav Das7bcbe702017-06-13 15:35:54 -07001790 return nextHops;
1791 }
1792 }
Saurav Dasc6ff8f02018-04-23 18:42:12 -07001793 log.debug("No next hops found for target:{} --> dst: {}", targetSw, dstSw);
Saurav Das7bcbe702017-06-13 15:35:54 -07001794 return ImmutableSet.of(); //no next-hops found
1795 }
1796
1797 /**
1798 * Represents two devices that are paired by configuration. An EdgePair for
1799 * (dev1, dev2) is the same as as EdgePair for (dev2, dev1)
1800 */
1801 protected final class EdgePair {
1802 DeviceId dev1;
1803 DeviceId dev2;
1804
1805 EdgePair(DeviceId dev1, DeviceId dev2) {
1806 this.dev1 = dev1;
1807 this.dev2 = dev2;
1808 }
1809
1810 boolean includes(DeviceId dev) {
1811 return dev1.equals(dev) || dev2.equals(dev);
1812 }
1813
1814 @Override
1815 public boolean equals(Object o) {
1816 if (this == o) {
1817 return true;
1818 }
1819 if (!(o instanceof EdgePair)) {
1820 return false;
1821 }
1822 EdgePair that = (EdgePair) o;
1823 return ((this.dev1.equals(that.dev1) && this.dev2.equals(that.dev2)) ||
1824 (this.dev1.equals(that.dev2) && this.dev2.equals(that.dev1)));
1825 }
1826
1827 @Override
1828 public int hashCode() {
1829 if (dev1.toString().compareTo(dev2.toString()) <= 0) {
1830 return Objects.hash(dev1, dev2);
1831 } else {
1832 return Objects.hash(dev2, dev1);
1833 }
1834 }
1835
1836 @Override
1837 public String toString() {
1838 return toStringHelper(this)
1839 .add("Dev1", dev1)
1840 .add("Dev2", dev2)
1841 .toString();
1842 }
1843 }
1844
1845 //////////////////////////////////////
1846 // Filtering rule creation
1847 //////////////////////////////////////
1848
1849 /**
Saurav Das018605f2017-02-18 14:05:44 -08001850 * Populates filtering rules for port, and punting rules
1851 * for gateway IPs, loopback IPs and arp/ndp traffic.
1852 * Should only be called by the master instance for this device/port.
sanghob35a6192015-04-01 13:05:26 -07001853 *
1854 * @param deviceId Switch ID to set the rules
1855 */
Charles Chan50bb6ef2018-04-18 18:41:05 -07001856 void populatePortAddressingRules(DeviceId deviceId) {
Saurav Das59232cf2016-04-27 18:35:50 -07001857 // Although device is added, sometimes device store does not have the
1858 // ports for this device yet. It results in missing filtering rules in the
1859 // switch. We will attempt it a few times. If it still does not work,
1860 // user can manually repopulate using CLI command sr-reroute-network
Charles Chanf6ec1532017-02-08 16:10:40 -08001861 PortFilterInfo firstRun = rulePopulator.populateVlanMacFilters(deviceId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001862 if (firstRun == null) {
1863 firstRun = new PortFilterInfo(0, 0, 0);
Saurav Das59232cf2016-04-27 18:35:50 -07001864 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001865 executorService.schedule(new RetryFilters(deviceId, firstRun),
1866 RETRY_INTERVAL_MS, TimeUnit.MILLISECONDS);
sanghob35a6192015-04-01 13:05:26 -07001867 }
1868
1869 /**
Saurav Dasd2fded02016-12-02 15:43:47 -08001870 * Utility class used to temporarily store information about the ports on a
1871 * device processed for filtering objectives.
Saurav Dasd2fded02016-12-02 15:43:47 -08001872 */
1873 public final class PortFilterInfo {
Saurav Das018605f2017-02-18 14:05:44 -08001874 int disabledPorts = 0, errorPorts = 0, filteredPorts = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001875
Saurav Das018605f2017-02-18 14:05:44 -08001876 public PortFilterInfo(int disabledPorts, int errorPorts,
Saurav Dasd2fded02016-12-02 15:43:47 -08001877 int filteredPorts) {
1878 this.disabledPorts = disabledPorts;
1879 this.filteredPorts = filteredPorts;
Saurav Das018605f2017-02-18 14:05:44 -08001880 this.errorPorts = errorPorts;
Saurav Dasd2fded02016-12-02 15:43:47 -08001881 }
1882
1883 @Override
1884 public int hashCode() {
Saurav Das018605f2017-02-18 14:05:44 -08001885 return Objects.hash(disabledPorts, filteredPorts, errorPorts);
Saurav Dasd2fded02016-12-02 15:43:47 -08001886 }
1887
1888 @Override
1889 public boolean equals(Object obj) {
1890 if (this == obj) {
1891 return true;
1892 }
1893 if ((obj == null) || (!(obj instanceof PortFilterInfo))) {
1894 return false;
1895 }
1896 PortFilterInfo other = (PortFilterInfo) obj;
1897 return ((disabledPorts == other.disabledPorts) &&
1898 (filteredPorts == other.filteredPorts) &&
Saurav Das018605f2017-02-18 14:05:44 -08001899 (errorPorts == other.errorPorts));
Saurav Dasd2fded02016-12-02 15:43:47 -08001900 }
1901
1902 @Override
1903 public String toString() {
1904 MoreObjects.ToStringHelper helper = toStringHelper(this)
1905 .add("disabledPorts", disabledPorts)
Saurav Das018605f2017-02-18 14:05:44 -08001906 .add("errorPorts", errorPorts)
Saurav Dasd2fded02016-12-02 15:43:47 -08001907 .add("filteredPorts", filteredPorts);
1908 return helper.toString();
1909 }
1910 }
1911
1912 /**
1913 * RetryFilters populates filtering objectives for a device and keeps retrying
1914 * till the number of ports filtered are constant for a predefined number
1915 * of attempts.
1916 */
1917 protected final class RetryFilters implements Runnable {
1918 int constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS;
1919 DeviceId devId;
1920 int counter;
1921 PortFilterInfo prevRun;
1922
1923 private RetryFilters(DeviceId deviceId, PortFilterInfo previousRun) {
Saurav Das59232cf2016-04-27 18:35:50 -07001924 devId = deviceId;
Saurav Dasd2fded02016-12-02 15:43:47 -08001925 prevRun = previousRun;
1926 counter = 0;
Saurav Das59232cf2016-04-27 18:35:50 -07001927 }
1928
1929 @Override
1930 public void run() {
Charles Chan7f9737b2017-06-22 14:27:17 -07001931 log.debug("RETRY FILTER ATTEMPT {} ** dev:{}", ++counter, devId);
Charles Chanf6ec1532017-02-08 16:10:40 -08001932 PortFilterInfo thisRun = rulePopulator.populateVlanMacFilters(devId);
Saurav Dasd2fded02016-12-02 15:43:47 -08001933 boolean sameResult = prevRun.equals(thisRun);
1934 log.debug("dev:{} prevRun:{} thisRun:{} sameResult:{}", devId, prevRun,
1935 thisRun, sameResult);
Ray Milkeyc6c9b172018-02-26 09:36:31 -08001936 if (thisRun == null || !sameResult || (--constantAttempts > 0)) {
Saurav Das018605f2017-02-18 14:05:44 -08001937 // exponentially increasing intervals for retries
1938 executorService.schedule(this,
1939 RETRY_INTERVAL_MS * (int) Math.pow(counter, RETRY_INTERVAL_SCALE),
1940 TimeUnit.MILLISECONDS);
Saurav Dasd2fded02016-12-02 15:43:47 -08001941 if (!sameResult) {
1942 constantAttempts = MAX_CONSTANT_RETRY_ATTEMPTS; //reset
1943 }
Saurav Das59232cf2016-04-27 18:35:50 -07001944 }
Saurav Dasd2fded02016-12-02 15:43:47 -08001945 prevRun = (thisRun == null) ? prevRun : thisRun;
Saurav Das59232cf2016-04-27 18:35:50 -07001946 }
Saurav Das59232cf2016-04-27 18:35:50 -07001947 }
1948
sanghob35a6192015-04-01 13:05:26 -07001949}