94abebaa111badbc94c4f2aa4c3a919b96f731fc
[onosfw.git] /
1 /*
2  * Copyright 2014-2015 Open Networking Laboratory
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 package org.onosproject.provider.lldp.impl;
17
18 import static com.google.common.base.Strings.isNullOrEmpty;
19 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
20 import static java.util.concurrent.TimeUnit.SECONDS;
21 import static org.onlab.packet.Ethernet.TYPE_BSN;
22 import static org.onlab.packet.Ethernet.TYPE_LLDP;
23 import static org.onlab.util.Tools.get;
24 import static org.onlab.util.Tools.groupedThreads;
25 import static org.onosproject.net.Link.Type.DIRECT;
26 import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
27 import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY;
28 import static org.onosproject.net.config.basics.SubjectFactories.DEVICE_SUBJECT_FACTORY;
29 import static org.slf4j.LoggerFactory.getLogger;
30
31 import java.util.Dictionary;
32 import java.util.EnumSet;
33 import java.util.Map;
34 import java.util.Optional;
35 import java.util.Properties;
36 import java.util.Set;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.ScheduledExecutorService;
39
40 import org.apache.felix.scr.annotations.Activate;
41 import org.apache.felix.scr.annotations.Component;
42 import org.apache.felix.scr.annotations.Deactivate;
43 import org.apache.felix.scr.annotations.Modified;
44 import org.apache.felix.scr.annotations.Property;
45 import org.apache.felix.scr.annotations.Reference;
46 import org.apache.felix.scr.annotations.ReferenceCardinality;
47 import org.onlab.packet.Ethernet;
48 import org.onosproject.cfg.ComponentConfigService;
49 import org.onosproject.cluster.ClusterService;
50 import org.onosproject.core.ApplicationId;
51 import org.onosproject.core.CoreService;
52 import org.onosproject.mastership.MastershipEvent;
53 import org.onosproject.mastership.MastershipListener;
54 import org.onosproject.mastership.MastershipService;
55 import org.onosproject.net.ConnectPoint;
56 import org.onosproject.net.Device;
57 import org.onosproject.net.DeviceId;
58 import org.onosproject.net.LinkKey;
59 import org.onosproject.net.Port;
60 import org.onosproject.net.config.ConfigFactory;
61 import org.onosproject.net.config.NetworkConfigEvent;
62 import org.onosproject.net.config.NetworkConfigListener;
63 import org.onosproject.net.config.NetworkConfigRegistry;
64 import org.onosproject.net.device.DeviceEvent;
65 import org.onosproject.net.device.DeviceListener;
66 import org.onosproject.net.device.DeviceService;
67 import org.onosproject.net.flow.DefaultTrafficSelector;
68 import org.onosproject.net.flow.TrafficSelector;
69 import org.onosproject.net.link.DefaultLinkDescription;
70 import org.onosproject.net.link.LinkProvider;
71 import org.onosproject.net.link.LinkProviderRegistry;
72 import org.onosproject.net.link.LinkProviderService;
73 import org.onosproject.net.link.LinkService;
74 import org.onosproject.net.packet.PacketContext;
75 import org.onosproject.net.packet.PacketPriority;
76 import org.onosproject.net.packet.PacketProcessor;
77 import org.onosproject.net.packet.PacketService;
78 import org.onosproject.net.provider.AbstractProvider;
79 import org.onosproject.net.provider.ProviderId;
80 import org.osgi.service.component.ComponentContext;
81 import org.slf4j.Logger;
82
83 import com.google.common.collect.ImmutableMap;
84 import com.google.common.collect.ImmutableSet;
85 import com.google.common.collect.Maps;
86
87 /**
88  * Provider which uses LLDP and BDDP packets to detect network infrastructure links.
89  */
90 @Component(immediate = true)
91 public class LldpLinkProvider extends AbstractProvider implements LinkProvider {
92
93     private static final String PROVIDER_NAME = "org.onosproject.provider.lldp";
94
95     private static final String FORMAT =
96             "Settings: enabled={}, useBDDP={}, probeRate={}, " +
97                     "staleLinkAge={}";
98
99     // When a Device/Port has this annotation, do not send out LLDP/BDDP
100     public static final String NO_LLDP = "no-lldp";
101
102     private final Logger log = getLogger(getClass());
103
104     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105     protected CoreService coreService;
106
107     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108     protected LinkProviderRegistry providerRegistry;
109
110     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111     protected DeviceService deviceService;
112
113     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114     protected LinkService linkService;
115
116     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117     protected PacketService packetService;
118
119     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120     protected MastershipService masterService;
121
122     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123     protected ComponentConfigService cfgService;
124
125     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126     protected ClusterService clusterService;
127
128     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129     protected NetworkConfigRegistry cfgRegistry;
130
131     private LinkProviderService providerService;
132
133     private ScheduledExecutorService executor;
134
135     // TODO: Add sanity checking for the configurable params based on the delays
136     private static final long DEVICE_SYNC_DELAY = 5;
137     private static final long LINK_PRUNER_DELAY = 3;
138
139     private static final String PROP_ENABLED = "enabled";
140     @Property(name = PROP_ENABLED, boolValue = true,
141             label = "If false, link discovery is disabled")
142     private boolean enabled = false;
143
144     private static final String PROP_USE_BDDP = "useBDDP";
145     @Property(name = PROP_USE_BDDP, boolValue = true,
146             label = "Use BDDP for link discovery")
147     private boolean useBddp = true;
148
149     private static final String PROP_PROBE_RATE = "probeRate";
150     private static final int DEFAULT_PROBE_RATE = 3_000;
151     @Property(name = PROP_PROBE_RATE, intValue = DEFAULT_PROBE_RATE,
152             label = "LLDP and BDDP probe rate specified in millis")
153     private int probeRate = DEFAULT_PROBE_RATE;
154
155     private static final String PROP_STALE_LINK_AGE = "staleLinkAge";
156     private static final int DEFAULT_STALE_LINK_AGE = 10_000;
157     @Property(name = PROP_STALE_LINK_AGE, intValue = DEFAULT_STALE_LINK_AGE,
158             label = "Number of millis beyond which links will be considered stale")
159     private int staleLinkAge = DEFAULT_STALE_LINK_AGE;
160
161     private final DiscoveryContext context = new InternalDiscoveryContext();
162     private final InternalRoleListener roleListener = new InternalRoleListener();
163     private final InternalDeviceListener deviceListener = new InternalDeviceListener();
164     private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
165
166     // Device link discovery helpers.
167     protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>();
168
169     // Most recent time a tracked link was seen; links are tracked if their
170     // destination connection point is mastered by this controller instance.
171     private final Map<LinkKey, Long> linkTimes = Maps.newConcurrentMap();
172
173     private ApplicationId appId;
174
175     static final SuppressionRules DEFAULT_RULES
176         = new SuppressionRules(EnumSet.of(Device.Type.ROADM),
177                                ImmutableMap.of(NO_LLDP, SuppressionRules.ANY_VALUE));
178
179     private SuppressionRules rules = LldpLinkProvider.DEFAULT_RULES;
180
181     public static final String CONFIG_KEY = "suppression";
182     public static final String FEATURE_NAME = "linkDiscovery";
183
184     private final Set<ConfigFactory<?, ?>> factories = ImmutableSet.of(
185             new ConfigFactory<ApplicationId, SuppressionConfig>(APP_SUBJECT_FACTORY,
186                     SuppressionConfig.class,
187                     CONFIG_KEY) {
188                 @Override
189                 public SuppressionConfig createConfig() {
190                     return new SuppressionConfig();
191                 }
192             },
193             new ConfigFactory<DeviceId, LinkDiscoveryFromDevice>(DEVICE_SUBJECT_FACTORY,
194                     LinkDiscoveryFromDevice.class, FEATURE_NAME) {
195                 @Override
196                 public LinkDiscoveryFromDevice createConfig() {
197                     return new LinkDiscoveryFromDevice();
198                 }
199             },
200             new ConfigFactory<ConnectPoint, LinkDiscoveryFromPort>(CONNECT_POINT_SUBJECT_FACTORY,
201                     LinkDiscoveryFromPort.class, FEATURE_NAME) {
202                 @Override
203                 public LinkDiscoveryFromPort createConfig() {
204                     return new LinkDiscoveryFromPort();
205                 }
206             }
207     );
208
209     private final InternalConfigListener cfgListener = new InternalConfigListener();
210
211
212     /**
213      * Creates an OpenFlow link provider.
214      */
215     public LldpLinkProvider() {
216         super(new ProviderId("lldp", PROVIDER_NAME));
217     }
218
219     @Activate
220     public void activate(ComponentContext context) {
221         cfgService.registerProperties(getClass());
222         appId = coreService.registerApplication(PROVIDER_NAME);
223
224         cfgRegistry.addListener(cfgListener);
225         factories.forEach(cfgRegistry::registerConfigFactory);
226
227         SuppressionConfig cfg = cfgRegistry.getConfig(appId, SuppressionConfig.class);
228         if (cfg == null) {
229             // If no configuration is found, register default.
230             cfg = cfgRegistry.addConfig(appId, SuppressionConfig.class);
231             cfg.deviceTypes(DEFAULT_RULES.getSuppressedDeviceType())
232                .annotation(DEFAULT_RULES.getSuppressedAnnotation())
233                .apply();
234         }
235         cfgListener.reconfigureSuppressionRules(cfg);
236
237         modified(context);
238         log.info("Started");
239     }
240
241     @Deactivate
242     public void deactivate() {
243         cfgRegistry.removeListener(cfgListener);
244         factories.forEach(cfgRegistry::unregisterConfigFactory);
245
246         cfgService.unregisterProperties(getClass(), false);
247         disable();
248         log.info("Stopped");
249     }
250
251     @Modified
252     public void modified(ComponentContext context) {
253         Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
254
255         boolean newEnabled, newUseBddp;
256         int newProbeRate, newStaleLinkAge;
257         try {
258             String s = get(properties, PROP_ENABLED);
259             newEnabled = isNullOrEmpty(s) || Boolean.parseBoolean(s.trim());
260
261             s = get(properties, PROP_USE_BDDP);
262             newUseBddp = isNullOrEmpty(s) || Boolean.parseBoolean(s.trim());
263
264             s = get(properties, PROP_PROBE_RATE);
265             newProbeRate = isNullOrEmpty(s) ? probeRate : Integer.parseInt(s.trim());
266
267             s = get(properties, PROP_STALE_LINK_AGE);
268             newStaleLinkAge = isNullOrEmpty(s) ? staleLinkAge : Integer.parseInt(s.trim());
269
270         } catch (NumberFormatException e) {
271             log.warn("Component configuration had invalid values", e);
272             newEnabled = enabled;
273             newUseBddp = useBddp;
274             newProbeRate = probeRate;
275             newStaleLinkAge = staleLinkAge;
276         }
277
278         boolean wasEnabled = enabled;
279
280         enabled = newEnabled;
281         useBddp = newUseBddp;
282         probeRate = newProbeRate;
283         staleLinkAge = newStaleLinkAge;
284
285         if (!wasEnabled && enabled) {
286             enable();
287         } else if (wasEnabled && !enabled) {
288             disable();
289         } else {
290             if (enabled) {
291                 // update all discovery helper state
292                 loadDevices();
293             }
294         }
295
296         log.info(FORMAT, enabled, useBddp, probeRate, staleLinkAge);
297     }
298
299     /**
300      * Enables link discovery processing.
301      */
302     private void enable() {
303         providerService = providerRegistry.register(this);
304         masterService.addListener(roleListener);
305         deviceService.addListener(deviceListener);
306         packetService.addProcessor(packetProcessor, PacketProcessor.advisor(0));
307
308         loadDevices();
309
310         executor = newSingleThreadScheduledExecutor(groupedThreads("onos/link", "discovery-%d"));
311         executor.scheduleAtFixedRate(new SyncDeviceInfoTask(),
312                                      DEVICE_SYNC_DELAY, DEVICE_SYNC_DELAY, SECONDS);
313         executor.scheduleAtFixedRate(new LinkPrunerTask(),
314                                      LINK_PRUNER_DELAY, LINK_PRUNER_DELAY, SECONDS);
315
316         requestIntercepts();
317     }
318
319     /**
320      * Disables link discovery processing.
321      */
322     private void disable() {
323         withdrawIntercepts();
324
325         providerRegistry.unregister(this);
326         masterService.removeListener(roleListener);
327         deviceService.removeListener(deviceListener);
328         packetService.removeProcessor(packetProcessor);
329
330
331         if (executor != null) {
332             executor.shutdownNow();
333         }
334         discoverers.values().forEach(LinkDiscovery::stop);
335         discoverers.clear();
336
337         providerService = null;
338     }
339
340     /**
341      * Loads available devices and registers their ports to be probed.
342      */
343     private void loadDevices() {
344         if (!enabled) {
345             return;
346         }
347         deviceService.getAvailableDevices()
348                 .forEach(d -> updateDevice(d)
349                                .ifPresent(ld -> updatePorts(ld, d.id())));
350     }
351
352     private boolean isBlacklisted(DeviceId did) {
353         LinkDiscoveryFromDevice cfg = cfgRegistry.getConfig(did, LinkDiscoveryFromDevice.class);
354         if (cfg == null) {
355             return false;
356         }
357         return !cfg.enabled();
358     }
359
360     private boolean isBlacklisted(ConnectPoint cp) {
361         // if parent device is blacklisted, so is the port
362         if (isBlacklisted(cp.deviceId())) {
363             return true;
364         }
365         LinkDiscoveryFromPort cfg = cfgRegistry.getConfig(cp, LinkDiscoveryFromPort.class);
366         if (cfg == null) {
367             return false;
368         }
369         return !cfg.enabled();
370     }
371
372     private boolean isBlacklisted(Port port) {
373         return isBlacklisted(new ConnectPoint(port.element().id(), port.number()));
374     }
375
376     /**
377      * Updates discovery helper for specified device.
378      *
379      * Adds and starts a discovery helper for specified device if enabled,
380      * calls {@link #removeDevice(DeviceId)} otherwise.
381      *
382      * @param device device to add
383      * @return discovery helper if discovery is enabled for the device
384      */
385     private Optional<LinkDiscovery> updateDevice(Device device) {
386         if (device == null) {
387             return Optional.empty();
388         }
389         if (rules.isSuppressed(device) || isBlacklisted(device.id())) {
390             log.trace("LinkDiscovery from {} disabled by configuration", device.id());
391             removeDevice(device.id());
392             return Optional.empty();
393         }
394         LinkDiscovery ld = discoverers.computeIfAbsent(device.id(),
395                                      did -> new LinkDiscovery(device, context));
396         if (ld.isStopped()) {
397             ld.start();
398         }
399         return Optional.of(ld);
400     }
401
402     /**
403      * Removes after stopping discovery helper for specified device.
404      * @param deviceId device to remove
405      */
406     private void removeDevice(final DeviceId deviceId) {
407         discoverers.computeIfPresent(deviceId, (did, ld) -> {
408             ld.stop();
409             return null;
410         });
411
412     }
413
414     /**
415      * Updates ports of the specified device to the specified discovery helper.
416      */
417     private void updatePorts(LinkDiscovery discoverer, DeviceId deviceId) {
418         deviceService.getPorts(deviceId).forEach(p -> updatePort(discoverer, p));
419     }
420
421     /**
422      * Updates discovery helper state of the specified port.
423      *
424      * Adds a port to the discovery helper if up and discovery is enabled,
425      * or calls {@link #removePort(Port)} otherwise.
426      */
427     private void updatePort(LinkDiscovery discoverer, Port port) {
428         if (port == null) {
429             return;
430         }
431         if (port.number().isLogical()) {
432             // silently ignore logical ports
433             return;
434         }
435
436         if (rules.isSuppressed(port) || isBlacklisted(port)) {
437             log.trace("LinkDiscovery from {} disabled by configuration", port);
438             removePort(port);
439             return;
440         }
441
442         // check if enabled and turn off discovery?
443         if (!port.isEnabled()) {
444             removePort(port);
445             return;
446         }
447
448         discoverer.addPort(port);
449     }
450
451     /**
452      * Removes a port from the specified discovery helper.
453      * @param port the port
454      */
455     private void removePort(Port port) {
456         if (port.element() instanceof Device) {
457             Device d = (Device) port.element();
458             LinkDiscovery ld = discoverers.get(d.id());
459             if (ld != null) {
460                 ld.removePort(port.number());
461             }
462         } else {
463             log.warn("Attempted to remove non-Device port", port);
464         }
465     }
466
467     /**
468      * Requests packet intercepts.
469      */
470     private void requestIntercepts() {
471         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
472         selector.matchEthType(TYPE_LLDP);
473         packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
474
475         selector.matchEthType(TYPE_BSN);
476         if (useBddp) {
477             packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
478         } else {
479             packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
480         }
481     }
482
483     /**
484      * Withdraws packet intercepts.
485      */
486     private void withdrawIntercepts() {
487         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
488         selector.matchEthType(TYPE_LLDP);
489         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
490         selector.matchEthType(TYPE_BSN);
491         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
492     }
493
494     protected SuppressionRules rules() {
495         return rules;
496     }
497
498     protected void updateRules(SuppressionRules newRules) {
499         if (!rules.equals(newRules)) {
500             rules = newRules;
501             loadDevices();
502         }
503     }
504
505     /**
506      * Processes device mastership role changes.
507      */
508     private class InternalRoleListener implements MastershipListener {
509         @Override
510         public void event(MastershipEvent event) {
511             if (MastershipEvent.Type.BACKUPS_CHANGED.equals(event.type())) {
512                 // only need new master events
513                 return;
514             }
515
516             DeviceId deviceId = event.subject();
517             Device device = deviceService.getDevice(deviceId);
518             if (device == null) {
519                 log.debug("Device {} doesn't exist, or isn't there yet", deviceId);
520                 return;
521             }
522             if (clusterService.getLocalNode().id().equals(event.roleInfo().master())) {
523                 updateDevice(device).ifPresent(ld -> updatePorts(ld, device.id()));
524             }
525         }
526     }
527
528     /**
529      * Processes device events.
530      */
531     private class InternalDeviceListener implements DeviceListener {
532         @Override
533         public void event(DeviceEvent event) {
534             Device device = event.subject();
535             Port port = event.port();
536             if (device == null) {
537                 log.error("Device is null.");
538                 return;
539             }
540             log.trace("{} {} {}", event.type(), event.subject(), event);
541             final DeviceId deviceId = device.id();
542             switch (event.type()) {
543                 case DEVICE_ADDED:
544                 case DEVICE_UPDATED:
545                     updateDevice(device).ifPresent(ld -> updatePorts(ld, deviceId));
546                     break;
547                 case PORT_ADDED:
548                 case PORT_UPDATED:
549                     if (port.isEnabled()) {
550                         updateDevice(device).ifPresent(ld -> updatePort(ld, port));
551                     } else {
552                         log.debug("Port down {}", port);
553                         removePort(port);
554                         providerService.linksVanished(new ConnectPoint(port.element().id(),
555                                                                        port.number()));
556                     }
557                     break;
558                 case PORT_REMOVED:
559                     log.debug("Port removed {}", port);
560                     removePort(port);
561                     providerService.linksVanished(new ConnectPoint(port.element().id(),
562                                                                    port.number()));
563                     break;
564                 case DEVICE_REMOVED:
565                 case DEVICE_SUSPENDED:
566                     log.debug("Device removed {}", deviceId);
567                     removeDevice(deviceId);
568                     providerService.linksVanished(deviceId);
569                     break;
570                 case DEVICE_AVAILABILITY_CHANGED:
571                     if (deviceService.isAvailable(deviceId)) {
572                         log.debug("Device up {}", deviceId);
573                         updateDevice(device);
574                     } else {
575                         log.debug("Device down {}", deviceId);
576                         removeDevice(deviceId);
577                         providerService.linksVanished(deviceId);
578                     }
579                     break;
580                 case PORT_STATS_UPDATED:
581                     break;
582                 default:
583                     log.debug("Unknown event {}", event);
584             }
585         }
586     }
587
588     /**
589      * Processes incoming packets.
590      */
591     private class InternalPacketProcessor implements PacketProcessor {
592         @Override
593         public void process(PacketContext context) {
594             if (context == null || context.isHandled()) {
595                 return;
596             }
597
598             Ethernet eth = context.inPacket().parsed();
599             if (eth == null || (eth.getEtherType() != TYPE_LLDP && eth.getEtherType() != TYPE_BSN)) {
600                 return;
601             }
602
603             LinkDiscovery ld = discoverers.get(context.inPacket().receivedFrom().deviceId());
604             if (ld == null) {
605                 return;
606             }
607
608             if (ld.handleLldp(context)) {
609                 context.block();
610             }
611         }
612     }
613
614     /**
615      * Auxiliary task to keep device ports up to date.
616      */
617     private final class SyncDeviceInfoTask implements Runnable {
618         @Override
619         public void run() {
620             if (Thread.currentThread().isInterrupted()) {
621                 log.info("Interrupted, quitting");
622                 return;
623             }
624             // check what deviceService sees, to see if we are missing anything
625             try {
626                 loadDevices();
627             } catch (Exception e) {
628                 // Catch all exceptions to avoid task being suppressed
629                 log.error("Exception thrown during synchronization process", e);
630             }
631         }
632     }
633
634     /**
635      * Auxiliary task for pruning stale links.
636      */
637     private class LinkPrunerTask implements Runnable {
638         @Override
639         public void run() {
640             if (Thread.currentThread().isInterrupted()) {
641                 log.info("Interrupted, quitting");
642                 return;
643             }
644
645             try {
646                 // TODO: There is still a slight possibility of mastership
647                 // change occurring right with link going stale. This will
648                 // result in the stale link not being pruned.
649                 Maps.filterEntries(linkTimes, e -> {
650                     if (!masterService.isLocalMaster(e.getKey().dst().deviceId())) {
651                         return true;
652                     }
653                     if (isStale(e.getValue())) {
654                         providerService.linkVanished(new DefaultLinkDescription(e.getKey().src(),
655                                                                                 e.getKey().dst(),
656                                                                                 DIRECT));
657                         return true;
658                     }
659                     return false;
660                 }).clear();
661
662             } catch (Exception e) {
663                 // Catch all exceptions to avoid task being suppressed
664                 log.error("Exception thrown during link pruning process", e);
665             }
666         }
667
668         private boolean isStale(long lastSeen) {
669             return lastSeen < System.currentTimeMillis() - staleLinkAge;
670         }
671     }
672
673     /**
674      * Provides processing context for the device link discovery helpers.
675      */
676     private class InternalDiscoveryContext implements DiscoveryContext {
677         @Override
678         public MastershipService mastershipService() {
679             return masterService;
680         }
681
682         @Override
683         public LinkProviderService providerService() {
684             return providerService;
685         }
686
687         @Override
688         public PacketService packetService() {
689             return packetService;
690         }
691
692         @Override
693         public long probeRate() {
694             return probeRate;
695         }
696
697         @Override
698         public boolean useBddp() {
699             return useBddp;
700         }
701
702         @Override
703         public void touchLink(LinkKey key) {
704             linkTimes.put(key, System.currentTimeMillis());
705         }
706     }
707
708     static final EnumSet<NetworkConfigEvent.Type> CONFIG_CHANGED
709                     = EnumSet.of(NetworkConfigEvent.Type.CONFIG_ADDED,
710                                  NetworkConfigEvent.Type.CONFIG_UPDATED,
711                                  NetworkConfigEvent.Type.CONFIG_REMOVED);
712
713     private class InternalConfigListener implements NetworkConfigListener {
714
715         private synchronized void reconfigureSuppressionRules(SuppressionConfig cfg) {
716             if (cfg == null) {
717                 log.error("Suppression Config is null.");
718                 return;
719             }
720
721             SuppressionRules newRules = new SuppressionRules(cfg.deviceTypes(),
722                                                              cfg.annotation());
723
724             updateRules(newRules);
725         }
726
727         @Override
728         public void event(NetworkConfigEvent event) {
729             if (event.configClass() == LinkDiscoveryFromDevice.class &&
730                 CONFIG_CHANGED.contains(event.type())) {
731
732                 if (event.subject() instanceof DeviceId) {
733                     final DeviceId did = (DeviceId) event.subject();
734                     Device device = deviceService.getDevice(did);
735                     updateDevice(device).ifPresent(ld -> updatePorts(ld, did));
736                 }
737
738             } else if (event.configClass() == LinkDiscoveryFromPort.class &&
739                        CONFIG_CHANGED.contains(event.type())) {
740
741                 if (event.subject() instanceof ConnectPoint) {
742                     ConnectPoint cp = (ConnectPoint) event.subject();
743                     if (cp.elementId() instanceof DeviceId) {
744                         final DeviceId did = (DeviceId) cp.elementId();
745                         Device device = deviceService.getDevice(did);
746                         Port port = deviceService.getPort(did, cp.port());
747                         updateDevice(device).ifPresent(ld -> updatePort(ld, port));
748                     }
749                 }
750
751             } else if (event.configClass().equals(SuppressionConfig.class) &&
752                 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
753                  event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) {
754                 SuppressionConfig cfg = cfgRegistry.getConfig(appId, SuppressionConfig.class);
755                 reconfigureSuppressionRules(cfg);
756                 log.trace("Network config reconfigured");
757             }
758         }
759     }
760 }