386d838fd6a7f405ce08b25c6916bfd349d2c702
[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 com.google.common.collect.ImmutableMap;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Maps;
21 import org.apache.felix.scr.annotations.Activate;
22 import org.apache.felix.scr.annotations.Component;
23 import org.apache.felix.scr.annotations.Deactivate;
24 import org.apache.felix.scr.annotations.Modified;
25 import org.apache.felix.scr.annotations.Property;
26 import org.apache.felix.scr.annotations.Reference;
27 import org.apache.felix.scr.annotations.ReferenceCardinality;
28 import org.onlab.packet.Ethernet;
29 import org.onosproject.cfg.ComponentConfigService;
30 import org.onosproject.core.ApplicationId;
31 import org.onosproject.core.CoreService;
32 import org.onosproject.mastership.MastershipEvent;
33 import org.onosproject.mastership.MastershipListener;
34 import org.onosproject.mastership.MastershipService;
35 import org.onosproject.net.ConnectPoint;
36 import org.onosproject.net.Device;
37 import org.onosproject.net.DeviceId;
38 import org.onosproject.net.LinkKey;
39 import org.onosproject.net.Port;
40 import org.onosproject.net.device.DeviceEvent;
41 import org.onosproject.net.device.DeviceListener;
42 import org.onosproject.net.device.DeviceService;
43 import org.onosproject.net.flow.DefaultTrafficSelector;
44 import org.onosproject.net.flow.TrafficSelector;
45 import org.onosproject.net.link.DefaultLinkDescription;
46 import org.onosproject.net.link.LinkProvider;
47 import org.onosproject.net.link.LinkProviderRegistry;
48 import org.onosproject.net.link.LinkProviderService;
49 import org.onosproject.net.link.LinkService;
50 import org.onosproject.net.packet.PacketContext;
51 import org.onosproject.net.packet.PacketPriority;
52 import org.onosproject.net.packet.PacketProcessor;
53 import org.onosproject.net.packet.PacketService;
54 import org.onosproject.net.provider.AbstractProvider;
55 import org.onosproject.net.provider.ProviderId;
56 import org.osgi.service.component.ComponentContext;
57 import org.slf4j.Logger;
58
59 import java.io.IOException;
60 import java.util.Dictionary;
61 import java.util.EnumSet;
62 import java.util.Map;
63 import java.util.Properties;
64 import java.util.concurrent.ConcurrentHashMap;
65 import java.util.concurrent.ScheduledExecutorService;
66
67 import static com.google.common.base.Strings.isNullOrEmpty;
68 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
69 import static java.util.concurrent.TimeUnit.SECONDS;
70 import static org.onlab.util.Tools.get;
71 import static org.onlab.util.Tools.groupedThreads;
72 import static org.onosproject.net.Link.Type.DIRECT;
73 import static org.slf4j.LoggerFactory.getLogger;
74
75 /**
76  * Provider which uses LLDP and BDDP packets to detect network infrastructure links.
77  */
78 @Component(immediate = true)
79 public class LLDPLinkProvider extends AbstractProvider implements LinkProvider {
80
81     private static final String PROVIDER_NAME = "org.onosproject.provider.lldp";
82
83     private static final String FORMAT =
84             "Settings: enabled={}, useBDDP={}, probeRate={}, " +
85                     "staleLinkAge={}, lldpSuppression={}";
86
87     private final Logger log = getLogger(getClass());
88
89     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90     protected CoreService coreService;
91
92     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93     protected LinkProviderRegistry providerRegistry;
94
95     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96     protected DeviceService deviceService;
97
98     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99     protected LinkService linkService;
100
101     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102     protected PacketService packetService;
103
104     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105     protected MastershipService masterService;
106
107     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108     protected ComponentConfigService cfgService;
109
110     private LinkProviderService providerService;
111
112     private ScheduledExecutorService executor;
113
114     // TODO: Add sanity checking for the configurable params based on the delays
115     private static final long DEVICE_SYNC_DELAY = 5;
116     private static final long LINK_PRUNER_DELAY = 3;
117
118     private static final String PROP_ENABLED = "enabled";
119     @Property(name = PROP_ENABLED, boolValue = true,
120             label = "If false, link discovery is disabled")
121     private boolean enabled = false;
122
123     private static final String PROP_USE_BDDP = "useBDDP";
124     @Property(name = PROP_USE_BDDP, boolValue = true,
125             label = "Use BDDP for link discovery")
126     private boolean useBDDP = true;
127
128     private static final String PROP_PROBE_RATE = "probeRate";
129     private static final int DEFAULT_PROBE_RATE = 3_000;
130     @Property(name = PROP_PROBE_RATE, intValue = DEFAULT_PROBE_RATE,
131             label = "LLDP and BDDP probe rate specified in millis")
132     private int probeRate = DEFAULT_PROBE_RATE;
133
134     private static final String PROP_STALE_LINK_AGE = "staleLinkAge";
135     private static final int DEFAULT_STALE_LINK_AGE = 10_000;
136     @Property(name = PROP_STALE_LINK_AGE, intValue = DEFAULT_STALE_LINK_AGE,
137             label = "Number of millis beyond which links will be considered stale")
138     private int staleLinkAge = DEFAULT_STALE_LINK_AGE;
139
140     // FIXME: convert to use network config subsystem instead
141     private static final String PROP_LLDP_SUPPRESSION = "lldpSuppression";
142     private static final String DEFAULT_LLDP_SUPPRESSION_CONFIG = "../config/lldp_suppression.json";
143     @Property(name = PROP_LLDP_SUPPRESSION, value = DEFAULT_LLDP_SUPPRESSION_CONFIG,
144             label = "Path to LLDP suppression configuration file")
145     private String lldpSuppression = DEFAULT_LLDP_SUPPRESSION_CONFIG;
146
147     private final DiscoveryContext context = new InternalDiscoveryContext();
148     private final InternalRoleListener roleListener = new InternalRoleListener();
149     private final InternalDeviceListener deviceListener = new InternalDeviceListener();
150     private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
151
152     // Device link discovery helpers.
153     protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>();
154
155     // Most recent time a tracked link was seen; links are tracked if their
156     // destination connection point is mastered by this controller instance.
157     private final Map<LinkKey, Long> linkTimes = Maps.newConcurrentMap();
158
159     private SuppressionRules rules;
160     private ApplicationId appId;
161
162     /**
163      * Creates an OpenFlow link provider.
164      */
165     public LLDPLinkProvider() {
166         super(new ProviderId("lldp", PROVIDER_NAME));
167     }
168
169     @Activate
170     public void activate(ComponentContext context) {
171         cfgService.registerProperties(getClass());
172         appId = coreService.registerApplication(PROVIDER_NAME);
173         modified(context);
174         log.info("Started");
175     }
176
177     @Deactivate
178     public void deactivate() {
179         cfgService.unregisterProperties(getClass(), false);
180         disable();
181         log.info("Stopped");
182     }
183
184     @Modified
185     public void modified(ComponentContext context) {
186         Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
187
188         boolean newEnabled, newUseBddp;
189         int newProbeRate, newStaleLinkAge;
190         String newLldpSuppression;
191         try {
192             String s = get(properties, PROP_ENABLED);
193             newEnabled = isNullOrEmpty(s) || Boolean.parseBoolean(s.trim());
194
195             s = get(properties, PROP_USE_BDDP);
196             newUseBddp = isNullOrEmpty(s) || Boolean.parseBoolean(s.trim());
197
198             s = get(properties, PROP_PROBE_RATE);
199             newProbeRate = isNullOrEmpty(s) ? probeRate : Integer.parseInt(s.trim());
200
201             s = get(properties, PROP_STALE_LINK_AGE);
202             newStaleLinkAge = isNullOrEmpty(s) ? staleLinkAge : Integer.parseInt(s.trim());
203
204             s = get(properties, PROP_LLDP_SUPPRESSION);
205             newLldpSuppression = isNullOrEmpty(s) ? DEFAULT_LLDP_SUPPRESSION_CONFIG : s;
206
207         } catch (NumberFormatException e) {
208             log.warn(e.getMessage());
209             newEnabled = enabled;
210             newUseBddp = useBDDP;
211             newProbeRate = probeRate;
212             newStaleLinkAge = staleLinkAge;
213             newLldpSuppression = lldpSuppression;
214         }
215
216         boolean wasEnabled = enabled;
217
218         enabled = newEnabled;
219         useBDDP = newUseBddp;
220         probeRate = newProbeRate;
221         staleLinkAge = newStaleLinkAge;
222         lldpSuppression = newLldpSuppression;
223
224         if (!wasEnabled && enabled) {
225             enable();
226         } else if (wasEnabled && !enabled) {
227             disable();
228         }
229
230         log.info(FORMAT, enabled, useBDDP, probeRate, staleLinkAge, lldpSuppression);
231     }
232
233     /**
234      * Enables link discovery processing.
235      */
236     private void enable() {
237         providerService = providerRegistry.register(this);
238         masterService.addListener(roleListener);
239         deviceService.addListener(deviceListener);
240         packetService.addProcessor(packetProcessor, PacketProcessor.advisor(0));
241
242         loadSuppressionRules();
243         loadDevices();
244
245         executor = newSingleThreadScheduledExecutor(groupedThreads("onos/link", "discovery-%d"));
246         executor.scheduleAtFixedRate(new SyncDeviceInfoTask(),
247                                      DEVICE_SYNC_DELAY, DEVICE_SYNC_DELAY, SECONDS);
248         executor.scheduleAtFixedRate(new LinkPrunerTask(),
249                                      LINK_PRUNER_DELAY, LINK_PRUNER_DELAY, SECONDS);
250
251         requestIntercepts();
252     }
253
254     /**
255      * Disables link discovery processing.
256      */
257     private void disable() {
258         withdrawIntercepts();
259
260         providerRegistry.unregister(this);
261         masterService.removeListener(roleListener);
262         deviceService.removeListener(deviceListener);
263         packetService.removeProcessor(packetProcessor);
264
265         if (executor != null) {
266             executor.shutdownNow();
267         }
268         discoverers.values().forEach(LinkDiscovery::stop);
269         discoverers.clear();
270
271         providerService = null;
272     }
273
274     /**
275      * Loads available devices and registers their ports to be probed.
276      */
277     private void loadDevices() {
278         for (Device device : deviceService.getAvailableDevices()) {
279             if (rules.isSuppressed(device)) {
280                 log.debug("LinkDiscovery from {} disabled by configuration", device.id());
281                 continue;
282             }
283             LinkDiscovery ld = new LinkDiscovery(device, context);
284             discoverers.put(device.id(), ld);
285             addPorts(ld, device.id());
286         }
287     }
288
289     /**
290      * Adds ports of the specified device to the specified discovery helper.
291      */
292     private void addPorts(LinkDiscovery discoverer, DeviceId deviceId) {
293         for (Port p : deviceService.getPorts(deviceId)) {
294             if (rules.isSuppressed(p)) {
295                 continue;
296             }
297             if (!p.number().isLogical()) {
298                 discoverer.addPort(p);
299             }
300         }
301     }
302
303
304     /**
305      * Loads LLDP suppression rules.
306      */
307     private void loadSuppressionRules() {
308         // FIXME: convert to use network configuration
309         SuppressionRulesStore store = new SuppressionRulesStore(lldpSuppression);
310         try {
311             log.info("Reading suppression rules from {}", lldpSuppression);
312             rules = store.read();
313         } catch (IOException e) {
314             log.info("Failed to load {}, using built-in rules", lldpSuppression);
315             // default rule to suppress ROADM to maintain compatibility
316             rules = new SuppressionRules(ImmutableSet.of(),
317                                          EnumSet.of(Device.Type.ROADM),
318                                          ImmutableMap.of());
319         }
320
321         // should refresh discoverers when we need dynamic reconfiguration
322     }
323
324     /**
325      * Requests packet intercepts.
326      */
327     private void requestIntercepts() {
328         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
329         selector.matchEthType(Ethernet.TYPE_LLDP);
330         packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
331
332         selector.matchEthType(Ethernet.TYPE_BSN);
333         if (useBDDP) {
334             packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
335         } else {
336             packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
337         }
338     }
339
340     /**
341      * Withdraws packet intercepts.
342      */
343     private void withdrawIntercepts() {
344         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
345         selector.matchEthType(Ethernet.TYPE_LLDP);
346         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
347         selector.matchEthType(Ethernet.TYPE_BSN);
348         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
349     }
350
351     /**
352      * Processes device mastership role changes.
353      */
354     private class InternalRoleListener implements MastershipListener {
355         @Override
356         public void event(MastershipEvent event) {
357             if (MastershipEvent.Type.BACKUPS_CHANGED.equals(event.type())) {
358                 // only need new master events
359                 return;
360             }
361
362             DeviceId deviceId = event.subject();
363             Device device = deviceService.getDevice(deviceId);
364             if (device == null) {
365                 log.debug("Device {} doesn't exist, or isn't there yet", deviceId);
366                 return;
367             }
368             if (rules.isSuppressed(device)) {
369                 return;
370             }
371             discoverers.computeIfAbsent(deviceId, k -> new LinkDiscovery(device, context));
372         }
373
374     }
375
376     /**
377      * Processes device events.
378      */
379     private class InternalDeviceListener implements DeviceListener {
380         @Override
381         public void event(DeviceEvent event) {
382             LinkDiscovery ld;
383             Device device = event.subject();
384             Port port = event.port();
385             if (device == null) {
386                 log.error("Device is null.");
387                 return;
388             }
389             log.trace("{} {} {}", event.type(), event.subject(), event);
390             final DeviceId deviceId = device.id();
391             switch (event.type()) {
392                 case DEVICE_ADDED:
393                 case DEVICE_UPDATED:
394                     synchronized (discoverers) {
395                         ld = discoverers.get(deviceId);
396                         if (ld == null) {
397                             if (rules.isSuppressed(device)) {
398                                 log.debug("LinkDiscovery from {} disabled by configuration", device.id());
399                                 return;
400                             }
401                             log.debug("Device added ({}) {}", event.type(), deviceId);
402                             discoverers.put(deviceId, new LinkDiscovery(device, context));
403                         } else {
404                             if (ld.isStopped()) {
405                                 log.debug("Device restarted ({}) {}", event.type(), deviceId);
406                                 ld.start();
407                             }
408                         }
409                     }
410                     break;
411                 case PORT_ADDED:
412                 case PORT_UPDATED:
413                     if (port.isEnabled()) {
414                         ld = discoverers.get(deviceId);
415                         if (ld == null) {
416                             return;
417                         }
418                         if (rules.isSuppressed(port)) {
419                             log.debug("LinkDiscovery from {}@{} disabled by configuration",
420                                       port.number(), device.id());
421                             return;
422                         }
423                         if (!port.number().isLogical()) {
424                             log.debug("Port added {}", port);
425                             ld.addPort(port);
426                         }
427                     } else {
428                         log.debug("Port down {}", port);
429                         ConnectPoint point = new ConnectPoint(deviceId, port.number());
430                         providerService.linksVanished(point);
431                     }
432                     break;
433                 case PORT_REMOVED:
434                     log.debug("Port removed {}", port);
435                     ConnectPoint point = new ConnectPoint(deviceId, port.number());
436                     providerService.linksVanished(point);
437
438                     break;
439                 case DEVICE_REMOVED:
440                 case DEVICE_SUSPENDED:
441                     log.debug("Device removed {}", deviceId);
442                     ld = discoverers.get(deviceId);
443                     if (ld == null) {
444                         return;
445                     }
446                     ld.stop();
447                     providerService.linksVanished(deviceId);
448                     break;
449                 case DEVICE_AVAILABILITY_CHANGED:
450                     ld = discoverers.get(deviceId);
451                     if (ld == null) {
452                         return;
453                     }
454                     if (deviceService.isAvailable(deviceId)) {
455                         log.debug("Device up {}", deviceId);
456                         ld.start();
457                     } else {
458                         providerService.linksVanished(deviceId);
459                         log.debug("Device down {}", deviceId);
460                         ld.stop();
461                     }
462                     break;
463                 case PORT_STATS_UPDATED:
464                     break;
465                 default:
466                     log.debug("Unknown event {}", event);
467             }
468         }
469     }
470
471     /**
472      * Processes incoming packets.
473      */
474     private class InternalPacketProcessor implements PacketProcessor {
475         @Override
476         public void process(PacketContext context) {
477             if (context == null) {
478                 return;
479             }
480             LinkDiscovery ld = discoverers.get(context.inPacket().receivedFrom().deviceId());
481             if (ld == null) {
482                 return;
483             }
484
485             if (ld.handleLLDP(context)) {
486                 context.block();
487             }
488         }
489     }
490
491     /**
492      * Auxiliary task to keep device ports up to date.
493      */
494     private final class SyncDeviceInfoTask implements Runnable {
495         @Override
496         public void run() {
497             if (Thread.currentThread().isInterrupted()) {
498                 log.info("Interrupted, quitting");
499                 return;
500             }
501             // check what deviceService sees, to see if we are missing anything
502             try {
503                 for (Device dev : deviceService.getDevices()) {
504                     if (rules.isSuppressed(dev)) {
505                         continue;
506                     }
507                     DeviceId did = dev.id();
508                     synchronized (discoverers) {
509                         LinkDiscovery ld = discoverers
510                                 .computeIfAbsent(did, k -> new LinkDiscovery(dev, context));
511                         addPorts(ld, did);
512                     }
513                 }
514             } catch (Exception e) {
515                 // Catch all exceptions to avoid task being suppressed
516                 log.error("Exception thrown during synchronization process", e);
517             }
518         }
519     }
520
521     /**
522      * Auxiliary task for pruning stale links.
523      */
524     private class LinkPrunerTask implements Runnable {
525         @Override
526         public void run() {
527             if (Thread.currentThread().isInterrupted()) {
528                 log.info("Interrupted, quitting");
529                 return;
530             }
531
532             try {
533                 // TODO: There is still a slight possibility of mastership
534                 // change occurring right with link going stale. This will
535                 // result in the stale link not being pruned.
536                 Maps.filterEntries(linkTimes, e -> {
537                     if (!masterService.isLocalMaster(e.getKey().dst().deviceId())) {
538                         return true;
539                     }
540                     if (isStale(e.getValue())) {
541                         providerService.linkVanished(new DefaultLinkDescription(e.getKey().src(),
542                                                                                 e.getKey().dst(),
543                                                                                 DIRECT));
544                         return true;
545                     }
546                     return false;
547                 }).clear();
548
549             } catch (Exception e) {
550                 // Catch all exceptions to avoid task being suppressed
551                 log.error("Exception thrown during link pruning process", e);
552             }
553         }
554
555         private boolean isStale(long lastSeen) {
556             return lastSeen < System.currentTimeMillis() - staleLinkAge;
557         }
558     }
559
560     /**
561      * Provides processing context for the device link discovery helpers.
562      */
563     private class InternalDiscoveryContext implements DiscoveryContext {
564         @Override
565         public MastershipService mastershipService() {
566             return masterService;
567         }
568
569         @Override
570         public LinkProviderService providerService() {
571             return providerService;
572         }
573
574         @Override
575         public PacketService packetService() {
576             return packetService;
577         }
578
579         @Override
580         public long probeRate() {
581             return probeRate;
582         }
583
584         @Override
585         public boolean useBDDP() {
586             return useBDDP;
587         }
588
589         @Override
590         public void touchLink(LinkKey key) {
591             linkTimes.put(key, System.currentTimeMillis());
592         }
593     }
594
595 }