blob: 948124d3576f32c20ceadc675498d04eab4f6ad4 [file] [log] [blame]
Saurav Dase6c448a2018-01-18 12:07:33 -08001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
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 */
16
17package org.onosproject.segmentrouting;
18
19import java.util.Map;
20import java.util.Map.Entry;
21import java.util.Set;
22import java.util.concurrent.ConcurrentHashMap;
Saurav Das6430f412018-01-25 09:49:01 -080023import java.util.stream.Collectors;
24
Saurav Dase6c448a2018-01-18 12:07:33 -080025import org.onosproject.net.Device;
26import org.onosproject.net.DeviceId;
27import org.onosproject.net.HostLocation;
28import org.onosproject.net.Link;
29import org.onosproject.net.PortNumber;
30import org.onosproject.net.link.LinkService;
31import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
32import org.onosproject.segmentrouting.config.DeviceConfiguration;
33import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
34import org.onosproject.store.service.EventuallyConsistentMap;
35import org.onosproject.store.service.EventuallyConsistentMapBuilder;
36import org.onosproject.store.service.WallClockTimestamp;
37import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39
Saurav Das6430f412018-01-25 09:49:01 -080040import com.google.common.collect.ImmutableMap;
Saurav Dase6c448a2018-01-18 12:07:33 -080041import com.google.common.collect.Sets;
42
43public class LinkHandler {
44 private static final Logger log = LoggerFactory.getLogger(LinkHandler.class);
45 protected final SegmentRoutingManager srManager;
46 protected LinkService linkService;
47
48 // Local store for all links seen and their present status, used for
Saurav Das6430f412018-01-25 09:49:01 -080049 // optimized routing. The existence of the link in the keys is enough to know
Saurav Dase6c448a2018-01-18 12:07:33 -080050 // if the link has been "seen-before" by this instance of the controller.
51 // The boolean value indicates if the link is currently up or not.
Saurav Das6430f412018-01-25 09:49:01 -080052 // Currently the optimized routing logic depends on "forgetting" a link
53 // when a switch goes down, but "remembering" it when only the link goes down.
Saurav Dase6c448a2018-01-18 12:07:33 -080054 private Map<Link, Boolean> seenLinks = new ConcurrentHashMap<>();
55
56 private EventuallyConsistentMap<DeviceId, Set<PortNumber>> downedPortStore = null;
57
58 /**
59 * Constructs the LinkHandler.
60 *
61 * @param srManager Segment Routing manager
62 */
63 LinkHandler(SegmentRoutingManager srManager) {
64 this.srManager = srManager;
65 linkService = srManager.linkService;
66 log.debug("Creating EC map downedportstore");
67 EventuallyConsistentMapBuilder<DeviceId, Set<PortNumber>> downedPortsMapBuilder
68 = srManager.storageService.eventuallyConsistentMapBuilder();
69 downedPortStore = downedPortsMapBuilder.withName("downedportstore")
70 .withSerializer(srManager.createSerializer())
71 .withTimestampProvider((k, v) -> new WallClockTimestamp())
72 .build();
73 log.trace("Current size {}", downedPortStore.size());
74 }
75
76 /**
77 * Constructs the LinkHandler for unit-testing.
78 *
79 * @param srManager SegmentRoutingManager
80 * @param linkService LinkService
81 */
82 LinkHandler(SegmentRoutingManager srManager, LinkService linkService) {
83 this.srManager = srManager;
84 this.linkService = linkService;
85 }
86
87 /**
Saurav Dase321cff2018-02-09 17:26:45 -080088 * Initialize LinkHandler.
89 */
Charles Chanc12c3d02018-03-09 15:53:44 -080090 void init() {
Saurav Dase321cff2018-02-09 17:26:45 -080091 log.info("Loading stored links");
Charles Chanc12c3d02018-03-09 15:53:44 -080092 srManager.linkService.getActiveLinks().forEach(this::processLinkAdded);
Saurav Dase321cff2018-02-09 17:26:45 -080093 }
94
95 /**
Saurav Dase6c448a2018-01-18 12:07:33 -080096 * Preprocessing of added link before being sent for route-path handling.
97 * Also performs post processing of link.
98 *
99 * @param link the link to be processed
100 */
101 void processLinkAdded(Link link) {
102 log.info("** LINK ADDED {}", link.toString());
103 if (!isLinkValid(link)) {
104 return;
105 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800106 // Irrespective of whether the local is a MASTER or not for this device,
Saurav Das6430f412018-01-25 09:49:01 -0800107 // create group handler instance and push default TTP flow rules if needed,
Saurav Dase6c448a2018-01-18 12:07:33 -0800108 // as in a multi-instance setup, instances can initiate groups for any
Saurav Das97241862018-02-14 14:14:54 -0800109 // device. Also update local groupHandler stores.
Saurav Dase6c448a2018-01-18 12:07:33 -0800110 DefaultGroupHandler groupHandler = srManager.groupHandlerMap
Saurav Das97241862018-02-14 14:14:54 -0800111 .get(link.src().deviceId());
Saurav Dase6c448a2018-01-18 12:07:33 -0800112 if (groupHandler != null) {
113 groupHandler.portUpForLink(link);
114 } else {
Saurav Dase6c448a2018-01-18 12:07:33 -0800115 Device device = srManager.deviceService.getDevice(link.src().deviceId());
116 if (device != null) {
Saurav Das97241862018-02-14 14:14:54 -0800117 log.warn("processLinkAdded: Link Added notification without "
118 + "Device Added event, still handling it");
Saurav Dase6c448a2018-01-18 12:07:33 -0800119 srManager.processDeviceAdded(device);
120 groupHandler = srManager.groupHandlerMap.get(link.src().deviceId());
Saurav Das97241862018-02-14 14:14:54 -0800121 if (groupHandler != null) {
122 groupHandler.portUpForLink(link);
123 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800124 }
125 }
Saurav Das97241862018-02-14 14:14:54 -0800126 if (srManager.deviceConfiguration == null ||
127 !srManager.deviceConfiguration.isConfigured(link.src().deviceId())) {
128 updateSeenLink(link, true);
129 log.warn("Source device of this link is not configured.. "
130 + "not processing further");
131 return;
132 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800133
134 /*
135 // process link only if it is bidirectional
136 if (!isBidirectional(link)) {
137 log.debug("Link not bidirectional.. waiting for other direction " +
138 "src {} --> dst {} ", link.dst(), link.src());
139 // note that if we are not processing for routing, it should at least
140 // be considered a seen-link
141 updateSeenLink(link, true); return;
142 }
143 //TODO ensure that rehash is still done correctly even if link is not processed for
144 //rerouting - perhaps rehash in both directions when it ultimately becomes bidi?
145 */
146
147 log.debug("Starting optimized route-path processing for added link "
148 + "{} --> {}", link.src(), link.dst());
149 boolean seenBefore = isSeenLink(link);
150 // seenLink updates will be done after route-path changes
151 srManager.defaultRoutingHandler
152 .populateRoutingRulesForLinkStatusChange(null, link, null);
153
154 if (srManager.mastershipService.isLocalMaster(link.src().deviceId())) {
155 // handle edge-ports for dual-homed hosts
156 updateDualHomedHostPorts(link, true);
157
Saurav Das6430f412018-01-25 09:49:01 -0800158 // It's possible that linkUp causes no route-path change as ECMP graph does
Saurav Dase6c448a2018-01-18 12:07:33 -0800159 // not change if the link is a parallel link (same src-dst as
Saurav Das6430f412018-01-25 09:49:01 -0800160 // another link). However we still need to update ECMP hash groups to include new buckets
Saurav Dase6c448a2018-01-18 12:07:33 -0800161 // for the link that has come up.
Ray Milkey43969b92018-01-24 10:41:14 -0800162 if (groupHandler != null) {
163 if (!seenBefore && isParallelLink(link)) {
164 // if link seen first time, we need to ensure hash-groups have
165 // all ports
166 log.debug("Attempting retryHash for paralled first-time link {}",
167 link);
168 groupHandler.retryHash(link, false, true);
169 } else {
170 // seen before-link
171 if (isParallelLink(link)) {
172 log.debug("Attempting retryHash for paralled seen-before "
173 + "link {}", link);
174 groupHandler.retryHash(link, false, false);
175 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800176 }
177 }
178 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800179 }
180
181 /**
182 * Preprocessing of removed link before being sent for route-path handling.
183 * Also performs post processing of link.
184 *
185 * @param link the link to be processed
186 */
187 void processLinkRemoved(Link link) {
188 log.info("** LINK REMOVED {}", link.toString());
189 if (!isLinkValid(link)) {
190 return;
191 }
192 // when removing links, update seen links first, before doing route-path
193 // changes
194 updateSeenLink(link, false);
Saurav Das6430f412018-01-25 09:49:01 -0800195 // handle edge-ports for dual-homed hosts
196 if (srManager.mastershipService.isLocalMaster(link.src().deviceId())) {
197 updateDualHomedHostPorts(link, false);
198 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800199
200 // device availability check helps to ensure that multiple link-removed
201 // events are actually treated as a single switch removed event.
202 // purgeSeenLink is necessary so we do rerouting (instead of rehashing)
203 // when switch comes back.
204 if (link.src().elementId() instanceof DeviceId
205 && !srManager.deviceService.isAvailable(link.src().deviceId())) {
206 purgeSeenLink(link);
207 return;
208 }
209 if (link.dst().elementId() instanceof DeviceId
210 && !srManager.deviceService.isAvailable(link.dst().deviceId())) {
211 purgeSeenLink(link);
212 return;
213 }
214
Saurav Dase6c448a2018-01-18 12:07:33 -0800215 log.debug("Starting optimized route-path processing for removed link "
216 + "{} --> {}", link.src(), link.dst());
217 srManager.defaultRoutingHandler
218 .populateRoutingRulesForLinkStatusChange(link, null, null);
219
Saurav Das97241862018-02-14 14:14:54 -0800220 // attempt rehashing for parallel links
Saurav Dase6c448a2018-01-18 12:07:33 -0800221 DefaultGroupHandler groupHandler = srManager.groupHandlerMap
222 .get(link.src().deviceId());
223 if (groupHandler != null) {
224 if (srManager.mastershipService.isLocalMaster(link.src().deviceId())
225 && isParallelLink(link)) {
226 log.debug("* retrying hash for parallel link removed:{}", link);
227 groupHandler.retryHash(link, true, false);
228 } else {
229 log.debug("Not attempting retry-hash for link removed: {} .. {}",
230 link,
231 (srManager.mastershipService.isLocalMaster(link.src()
232 .deviceId())) ? "not parallel"
233 : "not master");
234 }
Saurav Das97241862018-02-14 14:14:54 -0800235 // ensure local stores are updated after all rerouting or rehashing
236 groupHandler.portDownForLink(link);
Saurav Dase6c448a2018-01-18 12:07:33 -0800237 } else {
238 log.warn("group handler not found for dev:{} when removing link: {}",
239 link.src().deviceId(), link);
240 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800241 }
242
243 /**
244 * Checks validity of link. Examples of invalid links include
245 * indirect-links, links between ports on the same switch, and more.
246 *
247 * @param link the link to be processed
248 * @return true if valid link
249 */
Pier Luigid8a15162018-02-15 16:33:08 +0100250 boolean isLinkValid(Link link) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800251 if (link.type() != Link.Type.DIRECT) {
252 // NOTE: A DIRECT link might be transiently marked as INDIRECT
253 // if BDDP is received before LLDP. We can safely ignore that
254 // until the LLDP is received and the link is marked as DIRECT.
255 log.info("Ignore link {}->{}. Link type is {} instead of DIRECT.",
256 link.src(), link.dst(), link.type());
257 return false;
258 }
259 DeviceId srcId = link.src().deviceId();
260 DeviceId dstId = link.dst().deviceId();
261 if (srcId.equals(dstId)) {
262 log.warn("Links between ports on the same switch are not "
263 + "allowed .. ignoring link {}", link);
264 return false;
265 }
266 DeviceConfiguration devConfig = srManager.deviceConfiguration;
Saurav Dase321cff2018-02-09 17:26:45 -0800267 if (devConfig == null) {
268 log.warn("Cannot check validity of link without device config");
269 return true;
270 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800271 try {
Saurav Das97241862018-02-14 14:14:54 -0800272 /*if (!devConfig.isEdgeDevice(srcId)
Saurav Dase6c448a2018-01-18 12:07:33 -0800273 && !devConfig.isEdgeDevice(dstId)) {
274 // ignore links between spines
275 // XXX revisit when handling multi-stage fabrics
276 log.warn("Links between spines not allowed...ignoring "
277 + "link {}", link);
278 return false;
Saurav Das97241862018-02-14 14:14:54 -0800279 }*/
Saurav Dase6c448a2018-01-18 12:07:33 -0800280 if (devConfig.isEdgeDevice(srcId)
281 && devConfig.isEdgeDevice(dstId)) {
282 // ignore links between leaves if they are not pair-links
283 // XXX revisit if removing pair-link config or allowing more than
284 // one pair-link
285 if (devConfig.getPairDeviceId(srcId).equals(dstId)
286 && devConfig.getPairLocalPort(srcId)
287 .equals(link.src().port())
288 && devConfig.getPairLocalPort(dstId)
289 .equals(link.dst().port())) {
290 // found pair link - allow it
291 return true;
292 } else {
293 log.warn("Links between leaves other than pair-links are "
294 + "not allowed...ignoring link {}", link);
295 return false;
296 }
297 }
298 } catch (DeviceConfigNotFoundException e) {
299 // We still want to count the links in seenLinks even though there
300 // is no config. So we let it return true
301 log.warn("Could not check validity of link {} as subtending devices "
302 + "are not yet configured", link);
303 }
304 return true;
305 }
306
307 /**
308 * Administratively enables or disables edge ports if the link that was
309 * added or removed was the only uplink port from an edge device. Only edge
310 * ports that belong to dual-homed hosts are considered.
311 *
312 * @param link the link to be processed
313 * @param added true if link was added, false if link was removed
314 */
315 private void updateDualHomedHostPorts(Link link, boolean added) {
Saurav Das6430f412018-01-25 09:49:01 -0800316 if (!onlyUplink(link)) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800317 return;
318 }
319 if (added) {
320 // re-enable previously disabled ports on this dev
321 Set<PortNumber> p = downedPortStore.remove(link.src().deviceId());
322 if (p != null) {
323 log.warn("Link src {} -->dst {} added is the first uplink, "
324 + "enabling dual homed ports: {}", link.src().deviceId(),
325 link.dst().deviceId(), (p.isEmpty()) ? "no ports" : p);
326 p.forEach(pnum -> srManager.deviceAdminService
327 .changePortState(link.src().deviceId(), pnum, true));
328 }
329 } else {
330 // find dual homed hosts on this dev to disable
331 Set<PortNumber> dhp = srManager.hostHandler
332 .getDualHomedHostPorts(link.src().deviceId());
333 log.warn("Link src {} -->dst {} removed was the last uplink, "
334 + "disabling dual homed ports: {}", link.src().deviceId(),
335 link.dst().deviceId(), (dhp.isEmpty()) ? "no ports" : dhp);
336 dhp.forEach(pnum -> srManager.deviceAdminService
337 .changePortState(link.src().deviceId(), pnum, false));
338 if (!dhp.isEmpty()) {
339 // update global store
340 Set<PortNumber> p = downedPortStore.get(link.src().deviceId());
341 if (p == null) {
342 p = dhp;
343 } else {
344 p.addAll(dhp);
345 }
346 downedPortStore.put(link.src().deviceId(), p);
347 }
348 }
349 }
350
351 /**
Saurav Das6430f412018-01-25 09:49:01 -0800352 * Returns true if given link is the only active uplink from src-device of
Saurav Dase6c448a2018-01-18 12:07:33 -0800353 * link. An uplink is defined as a unidirectional link with src as
354 * edgeRouter and dst as non-edgeRouter.
355 *
356 * @param link
357 * @return true if given link is-the-first/was-the-last uplink from the src
358 * device
359 */
Saurav Das6430f412018-01-25 09:49:01 -0800360 private boolean onlyUplink(Link link) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800361 DeviceConfiguration devConfig = srManager.deviceConfiguration;
362 try {
Saurav Das6430f412018-01-25 09:49:01 -0800363 if (!devConfig.isEdgeDevice(link.src().deviceId())
364 || devConfig.isEdgeDevice(link.dst().deviceId())) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800365 return false;
366 }
Saurav Das6430f412018-01-25 09:49:01 -0800367 // note that using linkservice here would cause race conditions as
368 // more links can show up while the app is still processing the first one
369 Set<Link> devLinks = seenLinks.entrySet().stream()
370 .filter(entry -> entry.getKey().src().deviceId()
371 .equals(link.src().deviceId()))
372 .filter(entry -> entry.getValue())
373 .filter(entry -> !entry.getKey().equals(link))
374 .map(entry -> entry.getKey())
375 .collect(Collectors.toSet());
376
Saurav Dase6c448a2018-01-18 12:07:33 -0800377 for (Link l : devLinks) {
Saurav Das6430f412018-01-25 09:49:01 -0800378 if (devConfig.isEdgeDevice(l.dst().deviceId())) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800379 continue;
380 }
Saurav Das6430f412018-01-25 09:49:01 -0800381 log.debug("Link {} is not the only active uplink. Found another"
382 + "link {}", link, l);
383 return false;
Saurav Dase6c448a2018-01-18 12:07:33 -0800384 }
Saurav Das6430f412018-01-25 09:49:01 -0800385 log.debug("Link {} is the only uplink", link);
386 return true;
Saurav Dase6c448a2018-01-18 12:07:33 -0800387 } catch (DeviceConfigNotFoundException e) {
Saurav Das6430f412018-01-25 09:49:01 -0800388 log.warn("Unable to determine if link is only uplink"
Saurav Dase6c448a2018-01-18 12:07:33 -0800389 + e.getMessage());
390 }
391 return false;
392 }
393
394 /**
395 * Returns true if this controller instance has seen this link before. The
396 * link may not be currently up, but as long as the link had been seen
397 * before this method will return true. The one exception is when the link
398 * was indeed seen before, but this controller instance was forced to forget
399 * it by a call to purgeSeenLink method.
400 *
401 * @param link the infrastructure link being queried
402 * @return true if this controller instance has seen this link before
403 */
404 boolean isSeenLink(Link link) {
405 return seenLinks.containsKey(link);
406 }
407
408 /**
409 * Updates the seen link store. Updates can be for links that are currently
410 * available or not.
411 *
412 * @param link the link to update in the seen-link local store
413 * @param up the status of the link, true if up, false if down
414 */
415 void updateSeenLink(Link link, boolean up) {
416 seenLinks.put(link, up);
417 }
418
419 /**
420 * Returns the status of a seen-link (up or down). If the link has not been
421 * seen-before, a null object is returned.
422 *
423 * @param link the infrastructure link being queried
424 * @return null if the link was not seen-before; true if the seen-link is
425 * up; false if the seen-link is down
426 */
427 private Boolean isSeenLinkUp(Link link) {
428 return seenLinks.get(link);
429 }
430
431 /**
432 * Makes this controller instance forget a previously seen before link.
433 *
434 * @param link the infrastructure link to purge
435 */
436 private void purgeSeenLink(Link link) {
437 seenLinks.remove(link);
438 }
439
440 /**
441 * Returns the status of a link as parallel link. A parallel link is defined
442 * as a link which has common src and dst switches as another seen-link that
443 * is currently enabled. It is not necessary for the link being queried to
444 * be a seen-link.
445 *
446 * @param link the infrastructure link being queried
447 * @return true if a seen-link exists that is up, and shares the same src
448 * and dst switches as the link being queried
449 */
450 private boolean isParallelLink(Link link) {
451 for (Entry<Link, Boolean> seen : seenLinks.entrySet()) {
452 Link seenLink = seen.getKey();
453 if (seenLink.equals(link)) {
454 continue;
455 }
456 if (seenLink.src().deviceId().equals(link.src().deviceId())
457 && seenLink.dst().deviceId().equals(link.dst().deviceId())
458 && seen.getValue()) {
459 return true;
460 }
461 }
462 return false;
463 }
464
465 /**
466 * Returns true if the link being queried is a bidirectional link. A bidi
467 * link is defined as a link, whose reverse link - ie. the link in the
468 * reverse direction - has been seen-before and is up. It is not necessary
469 * for the link being queried to be a seen-link.
470 *
471 * @param link the infrastructure link being queried
472 * @return true if another unidirectional link exists in the reverse
473 * direction, has been seen-before and is up
474 */
475 boolean isBidirectional(Link link) {
476 Link reverseLink = linkService.getLink(link.dst(), link.src());
477 if (reverseLink == null) {
478 return false;
479 }
480 Boolean result = isSeenLinkUp(reverseLink);
481 if (result == null) {
482 return false;
483 }
484 return result.booleanValue();
485 }
486
487 /**
488 * Determines if the given link should be avoided in routing calculations by
489 * policy or design.
490 *
491 * @param link the infrastructure link being queried
492 * @return true if link should be avoided
493 */
494 boolean avoidLink(Link link) {
495 // XXX currently only avoids all pair-links. In the future can be
496 // extended to avoid any generic link
497 DeviceId src = link.src().deviceId();
498 PortNumber srcPort = link.src().port();
499 DeviceConfiguration devConfig = srManager.deviceConfiguration;
500 if (devConfig == null || !devConfig.isConfigured(src)) {
501 log.warn("Device {} not configured..cannot avoid link {}", src,
502 link);
503 return false;
504 }
505 DeviceId pairDev;
506 PortNumber pairLocalPort, pairRemotePort = null;
507 try {
508 pairDev = devConfig.getPairDeviceId(src);
509 pairLocalPort = devConfig.getPairLocalPort(src);
510 if (pairDev != null) {
511 pairRemotePort = devConfig
512 .getPairLocalPort(pairDev);
513 }
514 } catch (DeviceConfigNotFoundException e) {
515 log.warn("Pair dev for dev {} not configured..cannot avoid link {}",
516 src, link);
517 return false;
518 }
519
520 return srcPort.equals(pairLocalPort)
521 && link.dst().deviceId().equals(pairDev)
522 && link.dst().port().equals(pairRemotePort);
523 }
524
525 /**
526 * Cleans up internal LinkHandler stores.
527 *
528 * @param device the device that has been removed
529 */
530 void processDeviceRemoved(Device device) {
531 seenLinks.keySet()
532 .removeIf(key -> key.src().deviceId().equals(device.id())
533 || key.dst().deviceId().equals(device.id()));
534 }
535
536 /**
537 * Administratively disables the host location switchport if the edge device
538 * has no viable uplinks.
539 *
540 * @param loc one of the locations of the dual-homed host
541 */
542 void checkUplinksForDualHomedHosts(HostLocation loc) {
543 try {
544 for (Link l : srManager.linkService.getDeviceLinks(loc.deviceId())) {
545 if (srManager.deviceConfiguration.isEdgeDevice(l.dst().deviceId())
546 || l.state() == Link.State.INACTIVE) {
547 continue;
548 }
549 // found valid uplink - so, nothing to do
550 return;
551 }
552 } catch (DeviceConfigNotFoundException e) {
553 log.warn("Could not check for valid uplinks due to missing device"
554 + "config " + e.getMessage());
555 return;
556 }
557 log.warn("Dual homed host location {} has no valid uplinks; "
558 + "disabling dual homed port", loc);
559 srManager.deviceAdminService.changePortState(loc.deviceId(), loc.port(),
560 false);
561 Set<PortNumber> p = downedPortStore.get(loc.deviceId());
562 if (p == null) {
563 p = Sets.newHashSet(loc.port());
564 } else {
565 p.add(loc.port());
566 }
567 downedPortStore.put(loc.deviceId(), p);
568 }
569
Saurav Das6430f412018-01-25 09:49:01 -0800570 ImmutableMap<Link, Boolean> getSeenLinks() {
571 return ImmutableMap.copyOf(seenLinks);
572 }
573
574 ImmutableMap<DeviceId, Set<PortNumber>> getDownedPorts() {
575 return ImmutableMap.copyOf(downedPortStore.entrySet());
576 }
577
Saurav Dase6c448a2018-01-18 12:07:33 -0800578}