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