blob: 63473a7ec9286d71adf5c9f24df460afc54a3091 [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());
Saurav Dase321cff2018-02-09 17:26:45 -080074 init();
Saurav Dase6c448a2018-01-18 12:07:33 -080075 }
76
77 /**
78 * Constructs the LinkHandler for unit-testing.
79 *
80 * @param srManager SegmentRoutingManager
81 * @param linkService LinkService
82 */
83 LinkHandler(SegmentRoutingManager srManager, LinkService linkService) {
84 this.srManager = srManager;
85 this.linkService = linkService;
86 }
87
88 /**
Saurav Dase321cff2018-02-09 17:26:45 -080089 * Initialize LinkHandler.
90 */
91 private void init() {
92 log.info("Loading stored links");
93 srManager.linkService.getActiveLinks()
94 .forEach(link -> processLinkAdded(link));
95 }
96
97 /**
Saurav Dase6c448a2018-01-18 12:07:33 -080098 * Preprocessing of added link before being sent for route-path handling.
99 * Also performs post processing of link.
100 *
101 * @param link the link to be processed
102 */
103 void processLinkAdded(Link link) {
104 log.info("** LINK ADDED {}", link.toString());
105 if (!isLinkValid(link)) {
106 return;
107 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800108 // Irrespective of whether the local is a MASTER or not for this device,
Saurav Das6430f412018-01-25 09:49:01 -0800109 // create group handler instance and push default TTP flow rules if needed,
Saurav Dase6c448a2018-01-18 12:07:33 -0800110 // as in a multi-instance setup, instances can initiate groups for any
Saurav Das97241862018-02-14 14:14:54 -0800111 // device. Also update local groupHandler stores.
Saurav Dase6c448a2018-01-18 12:07:33 -0800112 DefaultGroupHandler groupHandler = srManager.groupHandlerMap
Saurav Das97241862018-02-14 14:14:54 -0800113 .get(link.src().deviceId());
Saurav Dase6c448a2018-01-18 12:07:33 -0800114 if (groupHandler != null) {
115 groupHandler.portUpForLink(link);
116 } else {
Saurav Dase6c448a2018-01-18 12:07:33 -0800117 Device device = srManager.deviceService.getDevice(link.src().deviceId());
118 if (device != null) {
Saurav Das97241862018-02-14 14:14:54 -0800119 log.warn("processLinkAdded: Link Added notification without "
120 + "Device Added event, still handling it");
Saurav Dase6c448a2018-01-18 12:07:33 -0800121 srManager.processDeviceAdded(device);
122 groupHandler = srManager.groupHandlerMap.get(link.src().deviceId());
Saurav Das97241862018-02-14 14:14:54 -0800123 if (groupHandler != null) {
124 groupHandler.portUpForLink(link);
125 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800126 }
127 }
Saurav Das97241862018-02-14 14:14:54 -0800128 if (srManager.deviceConfiguration == null ||
129 !srManager.deviceConfiguration.isConfigured(link.src().deviceId())) {
130 updateSeenLink(link, true);
131 log.warn("Source device of this link is not configured.. "
132 + "not processing further");
133 return;
134 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800135
136 /*
137 // process link only if it is bidirectional
138 if (!isBidirectional(link)) {
139 log.debug("Link not bidirectional.. waiting for other direction " +
140 "src {} --> dst {} ", link.dst(), link.src());
141 // note that if we are not processing for routing, it should at least
142 // be considered a seen-link
143 updateSeenLink(link, true); return;
144 }
145 //TODO ensure that rehash is still done correctly even if link is not processed for
146 //rerouting - perhaps rehash in both directions when it ultimately becomes bidi?
147 */
148
149 log.debug("Starting optimized route-path processing for added link "
150 + "{} --> {}", link.src(), link.dst());
151 boolean seenBefore = isSeenLink(link);
152 // seenLink updates will be done after route-path changes
153 srManager.defaultRoutingHandler
154 .populateRoutingRulesForLinkStatusChange(null, link, null);
155
156 if (srManager.mastershipService.isLocalMaster(link.src().deviceId())) {
157 // handle edge-ports for dual-homed hosts
158 updateDualHomedHostPorts(link, true);
159
Saurav Das6430f412018-01-25 09:49:01 -0800160 // It's possible that linkUp causes no route-path change as ECMP graph does
Saurav Dase6c448a2018-01-18 12:07:33 -0800161 // not change if the link is a parallel link (same src-dst as
Saurav Das6430f412018-01-25 09:49:01 -0800162 // another link). However we still need to update ECMP hash groups to include new buckets
Saurav Dase6c448a2018-01-18 12:07:33 -0800163 // for the link that has come up.
Ray Milkey43969b92018-01-24 10:41:14 -0800164 if (groupHandler != null) {
165 if (!seenBefore && isParallelLink(link)) {
166 // if link seen first time, we need to ensure hash-groups have
167 // all ports
168 log.debug("Attempting retryHash for paralled first-time link {}",
169 link);
170 groupHandler.retryHash(link, false, true);
171 } else {
172 // seen before-link
173 if (isParallelLink(link)) {
174 log.debug("Attempting retryHash for paralled seen-before "
175 + "link {}", link);
176 groupHandler.retryHash(link, false, false);
177 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800178 }
179 }
180 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800181 }
182
183 /**
184 * Preprocessing of removed link before being sent for route-path handling.
185 * Also performs post processing of link.
186 *
187 * @param link the link to be processed
188 */
189 void processLinkRemoved(Link link) {
190 log.info("** LINK REMOVED {}", link.toString());
191 if (!isLinkValid(link)) {
192 return;
193 }
194 // when removing links, update seen links first, before doing route-path
195 // changes
196 updateSeenLink(link, false);
Saurav Das6430f412018-01-25 09:49:01 -0800197 // handle edge-ports for dual-homed hosts
198 if (srManager.mastershipService.isLocalMaster(link.src().deviceId())) {
199 updateDualHomedHostPorts(link, false);
200 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800201
202 // device availability check helps to ensure that multiple link-removed
203 // events are actually treated as a single switch removed event.
204 // purgeSeenLink is necessary so we do rerouting (instead of rehashing)
205 // when switch comes back.
206 if (link.src().elementId() instanceof DeviceId
207 && !srManager.deviceService.isAvailable(link.src().deviceId())) {
208 purgeSeenLink(link);
209 return;
210 }
211 if (link.dst().elementId() instanceof DeviceId
212 && !srManager.deviceService.isAvailable(link.dst().deviceId())) {
213 purgeSeenLink(link);
214 return;
215 }
216
Saurav Dase6c448a2018-01-18 12:07:33 -0800217 log.debug("Starting optimized route-path processing for removed link "
218 + "{} --> {}", link.src(), link.dst());
219 srManager.defaultRoutingHandler
220 .populateRoutingRulesForLinkStatusChange(link, null, null);
221
Saurav Das97241862018-02-14 14:14:54 -0800222 // attempt rehashing for parallel links
Saurav Dase6c448a2018-01-18 12:07:33 -0800223 DefaultGroupHandler groupHandler = srManager.groupHandlerMap
224 .get(link.src().deviceId());
225 if (groupHandler != null) {
226 if (srManager.mastershipService.isLocalMaster(link.src().deviceId())
227 && isParallelLink(link)) {
228 log.debug("* retrying hash for parallel link removed:{}", link);
229 groupHandler.retryHash(link, true, false);
230 } else {
231 log.debug("Not attempting retry-hash for link removed: {} .. {}",
232 link,
233 (srManager.mastershipService.isLocalMaster(link.src()
234 .deviceId())) ? "not parallel"
235 : "not master");
236 }
Saurav Das97241862018-02-14 14:14:54 -0800237 // ensure local stores are updated after all rerouting or rehashing
238 groupHandler.portDownForLink(link);
Saurav Dase6c448a2018-01-18 12:07:33 -0800239 } else {
240 log.warn("group handler not found for dev:{} when removing link: {}",
241 link.src().deviceId(), link);
242 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800243 }
244
245 /**
246 * Checks validity of link. Examples of invalid links include
247 * indirect-links, links between ports on the same switch, and more.
248 *
249 * @param link the link to be processed
250 * @return true if valid link
251 */
Pier Luigid8a15162018-02-15 16:33:08 +0100252 boolean isLinkValid(Link link) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800253 if (link.type() != Link.Type.DIRECT) {
254 // NOTE: A DIRECT link might be transiently marked as INDIRECT
255 // if BDDP is received before LLDP. We can safely ignore that
256 // until the LLDP is received and the link is marked as DIRECT.
257 log.info("Ignore link {}->{}. Link type is {} instead of DIRECT.",
258 link.src(), link.dst(), link.type());
259 return false;
260 }
261 DeviceId srcId = link.src().deviceId();
262 DeviceId dstId = link.dst().deviceId();
263 if (srcId.equals(dstId)) {
264 log.warn("Links between ports on the same switch are not "
265 + "allowed .. ignoring link {}", link);
266 return false;
267 }
268 DeviceConfiguration devConfig = srManager.deviceConfiguration;
Saurav Dase321cff2018-02-09 17:26:45 -0800269 if (devConfig == null) {
270 log.warn("Cannot check validity of link without device config");
271 return true;
272 }
Saurav Dase6c448a2018-01-18 12:07:33 -0800273 try {
Saurav Das97241862018-02-14 14:14:54 -0800274 /*if (!devConfig.isEdgeDevice(srcId)
Saurav Dase6c448a2018-01-18 12:07:33 -0800275 && !devConfig.isEdgeDevice(dstId)) {
276 // ignore links between spines
277 // XXX revisit when handling multi-stage fabrics
278 log.warn("Links between spines not allowed...ignoring "
279 + "link {}", link);
280 return false;
Saurav Das97241862018-02-14 14:14:54 -0800281 }*/
Saurav Dase6c448a2018-01-18 12:07:33 -0800282 if (devConfig.isEdgeDevice(srcId)
283 && devConfig.isEdgeDevice(dstId)) {
284 // ignore links between leaves if they are not pair-links
285 // XXX revisit if removing pair-link config or allowing more than
286 // one pair-link
287 if (devConfig.getPairDeviceId(srcId).equals(dstId)
288 && devConfig.getPairLocalPort(srcId)
289 .equals(link.src().port())
290 && devConfig.getPairLocalPort(dstId)
291 .equals(link.dst().port())) {
292 // found pair link - allow it
293 return true;
294 } else {
295 log.warn("Links between leaves other than pair-links are "
296 + "not allowed...ignoring link {}", link);
297 return false;
298 }
299 }
300 } catch (DeviceConfigNotFoundException e) {
301 // We still want to count the links in seenLinks even though there
302 // is no config. So we let it return true
303 log.warn("Could not check validity of link {} as subtending devices "
304 + "are not yet configured", link);
305 }
306 return true;
307 }
308
309 /**
310 * Administratively enables or disables edge ports if the link that was
311 * added or removed was the only uplink port from an edge device. Only edge
312 * ports that belong to dual-homed hosts are considered.
313 *
314 * @param link the link to be processed
315 * @param added true if link was added, false if link was removed
316 */
317 private void updateDualHomedHostPorts(Link link, boolean added) {
Saurav Das6430f412018-01-25 09:49:01 -0800318 if (!onlyUplink(link)) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800319 return;
320 }
321 if (added) {
322 // re-enable previously disabled ports on this dev
323 Set<PortNumber> p = downedPortStore.remove(link.src().deviceId());
324 if (p != null) {
325 log.warn("Link src {} -->dst {} added is the first uplink, "
326 + "enabling dual homed ports: {}", link.src().deviceId(),
327 link.dst().deviceId(), (p.isEmpty()) ? "no ports" : p);
328 p.forEach(pnum -> srManager.deviceAdminService
329 .changePortState(link.src().deviceId(), pnum, true));
330 }
331 } else {
332 // find dual homed hosts on this dev to disable
333 Set<PortNumber> dhp = srManager.hostHandler
334 .getDualHomedHostPorts(link.src().deviceId());
335 log.warn("Link src {} -->dst {} removed was the last uplink, "
336 + "disabling dual homed ports: {}", link.src().deviceId(),
337 link.dst().deviceId(), (dhp.isEmpty()) ? "no ports" : dhp);
338 dhp.forEach(pnum -> srManager.deviceAdminService
339 .changePortState(link.src().deviceId(), pnum, false));
340 if (!dhp.isEmpty()) {
341 // update global store
342 Set<PortNumber> p = downedPortStore.get(link.src().deviceId());
343 if (p == null) {
344 p = dhp;
345 } else {
346 p.addAll(dhp);
347 }
348 downedPortStore.put(link.src().deviceId(), p);
349 }
350 }
351 }
352
353 /**
Saurav Das6430f412018-01-25 09:49:01 -0800354 * Returns true if given link is the only active uplink from src-device of
Saurav Dase6c448a2018-01-18 12:07:33 -0800355 * link. An uplink is defined as a unidirectional link with src as
356 * edgeRouter and dst as non-edgeRouter.
357 *
358 * @param link
359 * @return true if given link is-the-first/was-the-last uplink from the src
360 * device
361 */
Saurav Das6430f412018-01-25 09:49:01 -0800362 private boolean onlyUplink(Link link) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800363 DeviceConfiguration devConfig = srManager.deviceConfiguration;
364 try {
Saurav Das6430f412018-01-25 09:49:01 -0800365 if (!devConfig.isEdgeDevice(link.src().deviceId())
366 || devConfig.isEdgeDevice(link.dst().deviceId())) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800367 return false;
368 }
Saurav Das6430f412018-01-25 09:49:01 -0800369 // note that using linkservice here would cause race conditions as
370 // more links can show up while the app is still processing the first one
371 Set<Link> devLinks = seenLinks.entrySet().stream()
372 .filter(entry -> entry.getKey().src().deviceId()
373 .equals(link.src().deviceId()))
374 .filter(entry -> entry.getValue())
375 .filter(entry -> !entry.getKey().equals(link))
376 .map(entry -> entry.getKey())
377 .collect(Collectors.toSet());
378
Saurav Dase6c448a2018-01-18 12:07:33 -0800379 for (Link l : devLinks) {
Saurav Das6430f412018-01-25 09:49:01 -0800380 if (devConfig.isEdgeDevice(l.dst().deviceId())) {
Saurav Dase6c448a2018-01-18 12:07:33 -0800381 continue;
382 }
Saurav Das6430f412018-01-25 09:49:01 -0800383 log.debug("Link {} is not the only active uplink. Found another"
384 + "link {}", link, l);
385 return false;
Saurav Dase6c448a2018-01-18 12:07:33 -0800386 }
Saurav Das6430f412018-01-25 09:49:01 -0800387 log.debug("Link {} is the only uplink", link);
388 return true;
Saurav Dase6c448a2018-01-18 12:07:33 -0800389 } catch (DeviceConfigNotFoundException e) {
Saurav Das6430f412018-01-25 09:49:01 -0800390 log.warn("Unable to determine if link is only uplink"
Saurav Dase6c448a2018-01-18 12:07:33 -0800391 + e.getMessage());
392 }
393 return false;
394 }
395
396 /**
397 * Returns true if this controller instance has seen this link before. The
398 * link may not be currently up, but as long as the link had been seen
399 * before this method will return true. The one exception is when the link
400 * was indeed seen before, but this controller instance was forced to forget
401 * it by a call to purgeSeenLink method.
402 *
403 * @param link the infrastructure link being queried
404 * @return true if this controller instance has seen this link before
405 */
406 boolean isSeenLink(Link link) {
407 return seenLinks.containsKey(link);
408 }
409
410 /**
411 * Updates the seen link store. Updates can be for links that are currently
412 * available or not.
413 *
414 * @param link the link to update in the seen-link local store
415 * @param up the status of the link, true if up, false if down
416 */
417 void updateSeenLink(Link link, boolean up) {
418 seenLinks.put(link, up);
419 }
420
421 /**
422 * Returns the status of a seen-link (up or down). If the link has not been
423 * seen-before, a null object is returned.
424 *
425 * @param link the infrastructure link being queried
426 * @return null if the link was not seen-before; true if the seen-link is
427 * up; false if the seen-link is down
428 */
429 private Boolean isSeenLinkUp(Link link) {
430 return seenLinks.get(link);
431 }
432
433 /**
434 * Makes this controller instance forget a previously seen before link.
435 *
436 * @param link the infrastructure link to purge
437 */
438 private void purgeSeenLink(Link link) {
439 seenLinks.remove(link);
440 }
441
442 /**
443 * Returns the status of a link as parallel link. A parallel link is defined
444 * as a link which has common src and dst switches as another seen-link that
445 * is currently enabled. It is not necessary for the link being queried to
446 * be a seen-link.
447 *
448 * @param link the infrastructure link being queried
449 * @return true if a seen-link exists that is up, and shares the same src
450 * and dst switches as the link being queried
451 */
452 private boolean isParallelLink(Link link) {
453 for (Entry<Link, Boolean> seen : seenLinks.entrySet()) {
454 Link seenLink = seen.getKey();
455 if (seenLink.equals(link)) {
456 continue;
457 }
458 if (seenLink.src().deviceId().equals(link.src().deviceId())
459 && seenLink.dst().deviceId().equals(link.dst().deviceId())
460 && seen.getValue()) {
461 return true;
462 }
463 }
464 return false;
465 }
466
467 /**
468 * Returns true if the link being queried is a bidirectional link. A bidi
469 * link is defined as a link, whose reverse link - ie. the link in the
470 * reverse direction - has been seen-before and is up. It is not necessary
471 * for the link being queried to be a seen-link.
472 *
473 * @param link the infrastructure link being queried
474 * @return true if another unidirectional link exists in the reverse
475 * direction, has been seen-before and is up
476 */
477 boolean isBidirectional(Link link) {
478 Link reverseLink = linkService.getLink(link.dst(), link.src());
479 if (reverseLink == null) {
480 return false;
481 }
482 Boolean result = isSeenLinkUp(reverseLink);
483 if (result == null) {
484 return false;
485 }
486 return result.booleanValue();
487 }
488
489 /**
490 * Determines if the given link should be avoided in routing calculations by
491 * policy or design.
492 *
493 * @param link the infrastructure link being queried
494 * @return true if link should be avoided
495 */
496 boolean avoidLink(Link link) {
497 // XXX currently only avoids all pair-links. In the future can be
498 // extended to avoid any generic link
499 DeviceId src = link.src().deviceId();
500 PortNumber srcPort = link.src().port();
501 DeviceConfiguration devConfig = srManager.deviceConfiguration;
502 if (devConfig == null || !devConfig.isConfigured(src)) {
503 log.warn("Device {} not configured..cannot avoid link {}", src,
504 link);
505 return false;
506 }
507 DeviceId pairDev;
508 PortNumber pairLocalPort, pairRemotePort = null;
509 try {
510 pairDev = devConfig.getPairDeviceId(src);
511 pairLocalPort = devConfig.getPairLocalPort(src);
512 if (pairDev != null) {
513 pairRemotePort = devConfig
514 .getPairLocalPort(pairDev);
515 }
516 } catch (DeviceConfigNotFoundException e) {
517 log.warn("Pair dev for dev {} not configured..cannot avoid link {}",
518 src, link);
519 return false;
520 }
521
522 return srcPort.equals(pairLocalPort)
523 && link.dst().deviceId().equals(pairDev)
524 && link.dst().port().equals(pairRemotePort);
525 }
526
527 /**
528 * Cleans up internal LinkHandler stores.
529 *
530 * @param device the device that has been removed
531 */
532 void processDeviceRemoved(Device device) {
533 seenLinks.keySet()
534 .removeIf(key -> key.src().deviceId().equals(device.id())
535 || key.dst().deviceId().equals(device.id()));
536 }
537
538 /**
539 * Administratively disables the host location switchport if the edge device
540 * has no viable uplinks.
541 *
542 * @param loc one of the locations of the dual-homed host
543 */
544 void checkUplinksForDualHomedHosts(HostLocation loc) {
545 try {
546 for (Link l : srManager.linkService.getDeviceLinks(loc.deviceId())) {
547 if (srManager.deviceConfiguration.isEdgeDevice(l.dst().deviceId())
548 || l.state() == Link.State.INACTIVE) {
549 continue;
550 }
551 // found valid uplink - so, nothing to do
552 return;
553 }
554 } catch (DeviceConfigNotFoundException e) {
555 log.warn("Could not check for valid uplinks due to missing device"
556 + "config " + e.getMessage());
557 return;
558 }
559 log.warn("Dual homed host location {} has no valid uplinks; "
560 + "disabling dual homed port", loc);
561 srManager.deviceAdminService.changePortState(loc.deviceId(), loc.port(),
562 false);
563 Set<PortNumber> p = downedPortStore.get(loc.deviceId());
564 if (p == null) {
565 p = Sets.newHashSet(loc.port());
566 } else {
567 p.add(loc.port());
568 }
569 downedPortStore.put(loc.deviceId(), p);
570 }
571
Saurav Das6430f412018-01-25 09:49:01 -0800572 ImmutableMap<Link, Boolean> getSeenLinks() {
573 return ImmutableMap.copyOf(seenLinks);
574 }
575
576 ImmutableMap<DeviceId, Set<PortNumber>> getDownedPorts() {
577 return ImmutableMap.copyOf(downedPortStore.entrySet());
578 }
579
Saurav Dase6c448a2018-01-18 12:07:33 -0800580}