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