e9e2bfadefbfdf23990c766501e548bb6d4492ae
[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.base.Strings;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.ImmutableSet;
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.Port;
39 import org.onosproject.net.device.DeviceEvent;
40 import org.onosproject.net.device.DeviceListener;
41 import org.onosproject.net.device.DeviceService;
42 import org.onosproject.net.flow.DefaultTrafficSelector;
43 import org.onosproject.net.flow.TrafficSelector;
44 import org.onosproject.net.link.LinkProvider;
45 import org.onosproject.net.link.LinkProviderRegistry;
46 import org.onosproject.net.link.LinkProviderService;
47 import org.onosproject.net.packet.PacketContext;
48 import org.onosproject.net.packet.PacketPriority;
49 import org.onosproject.net.packet.PacketProcessor;
50 import org.onosproject.net.packet.PacketService;
51 import org.onosproject.net.provider.AbstractProvider;
52 import org.onosproject.net.provider.ProviderId;
53 import org.osgi.service.component.ComponentContext;
54 import org.slf4j.Logger;
55
56 import java.io.IOException;
57 import java.util.Dictionary;
58 import java.util.EnumSet;
59 import java.util.Map;
60 import java.util.concurrent.ConcurrentHashMap;
61 import java.util.concurrent.ScheduledExecutorService;
62
63 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
64 import static java.util.concurrent.TimeUnit.SECONDS;
65 import static org.onlab.util.Tools.get;
66 import static org.onlab.util.Tools.groupedThreads;
67 import static org.slf4j.LoggerFactory.getLogger;
68
69 /**
70  * Provider which uses an OpenFlow controller to detect network
71  * infrastructure links.
72  */
73 @Component(immediate = true)
74 public class LLDPLinkProvider extends AbstractProvider implements LinkProvider {
75
76     private static final String PROVIDER_NAME = "org.onosproject.provider.lldp";
77
78     private static final String PROP_USE_BDDP = "useBDDP";
79     private static final String PROP_DISABLE_LD = "disableLinkDiscovery";
80     private static final String PROP_LLDP_SUPPRESSION = "lldpSuppression";
81
82     private static final String DEFAULT_LLDP_SUPPRESSION_CONFIG = "../config/lldp_suppression.json";
83
84     private final Logger log = getLogger(getClass());
85
86     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87     protected CoreService coreService;
88
89     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90     protected LinkProviderRegistry providerRegistry;
91
92     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93     protected DeviceService deviceService;
94
95     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96     protected PacketService packetService;
97
98     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99     protected MastershipService masterService;
100
101     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102     protected ComponentConfigService cfgService;
103
104     private LinkProviderService providerService;
105
106     private ScheduledExecutorService executor;
107
108     @Property(name = PROP_USE_BDDP, boolValue = true,
109             label = "Use BDDP for link discovery")
110     private boolean useBDDP = true;
111
112     @Property(name = PROP_DISABLE_LD, boolValue = false,
113             label = "Permanently disable link discovery")
114     private boolean disableLinkDiscovery = false;
115
116     private static final long INIT_DELAY = 5;
117     private static final long DELAY = 5;
118
119     @Property(name = PROP_LLDP_SUPPRESSION, value = DEFAULT_LLDP_SUPPRESSION_CONFIG,
120             label = "Path to LLDP suppression configuration file")
121     private String lldpSuppression = DEFAULT_LLDP_SUPPRESSION_CONFIG;
122
123
124     private final InternalLinkProvider listener = new InternalLinkProvider();
125
126     private final InternalRoleListener roleListener = new InternalRoleListener();
127
128     protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>();
129
130     private SuppressionRules rules;
131     private ApplicationId appId;
132
133     /**
134      * Creates an OpenFlow link provider.
135      */
136     public LLDPLinkProvider() {
137         super(new ProviderId("lldp", PROVIDER_NAME));
138     }
139
140     @Activate
141     public void activate(ComponentContext context) {
142         cfgService.registerProperties(getClass());
143         appId = coreService.registerApplication(PROVIDER_NAME);
144
145         // to load configuration at startup
146         modified(context);
147         if (disableLinkDiscovery) {
148             log.info("LinkDiscovery has been permanently disabled by configuration");
149             return;
150         }
151
152         providerService = providerRegistry.register(this);
153         deviceService.addListener(listener);
154         packetService.addProcessor(listener, PacketProcessor.advisor(0));
155         masterService.addListener(roleListener);
156
157         LinkDiscovery ld;
158         for (Device device : deviceService.getAvailableDevices()) {
159             if (rules.isSuppressed(device)) {
160                 log.debug("LinkDiscovery from {} disabled by configuration", device.id());
161                 continue;
162             }
163             ld = new LinkDiscovery(device, packetService, masterService,
164                                    providerService, useBDDP);
165             discoverers.put(device.id(), ld);
166             addPorts(ld, device.id());
167         }
168
169         executor = newSingleThreadScheduledExecutor(groupedThreads("onos/device", "sync-%d"));
170         executor.scheduleAtFixedRate(new SyncDeviceInfoTask(), INIT_DELAY, DELAY, SECONDS);
171
172         requestIntercepts();
173
174         log.info("Started");
175     }
176
177     private void addPorts(LinkDiscovery discoverer, DeviceId deviceId) {
178         for (Port p : deviceService.getPorts(deviceId)) {
179             if (rules.isSuppressed(p)) {
180                 continue;
181             }
182             if (!p.number().isLogical()) {
183                 discoverer.addPort(p);
184             }
185         }
186     }
187
188     @Deactivate
189     public void deactivate() {
190         cfgService.unregisterProperties(getClass(), false);
191         if (disableLinkDiscovery) {
192             return;
193         }
194
195         withdrawIntercepts();
196
197         providerRegistry.unregister(this);
198         deviceService.removeListener(listener);
199         packetService.removeProcessor(listener);
200         masterService.removeListener(roleListener);
201
202         executor.shutdownNow();
203         discoverers.values().forEach(LinkDiscovery::stop);
204         discoverers.clear();
205         providerService = null;
206
207         log.info("Stopped");
208     }
209
210     @Modified
211     public void modified(ComponentContext context) {
212         if (context == null) {
213             loadSuppressionRules();
214             return;
215         }
216         @SuppressWarnings("rawtypes")
217         Dictionary properties = context.getProperties();
218
219         String s = get(properties, PROP_DISABLE_LD);
220         if (!Strings.isNullOrEmpty(s)) {
221             disableLinkDiscovery = Boolean.valueOf(s);
222         }
223         s = get(properties, PROP_USE_BDDP);
224         if (!Strings.isNullOrEmpty(s)) {
225             useBDDP = Boolean.valueOf(s);
226         }
227         s = get(properties, PROP_LLDP_SUPPRESSION);
228         if (!Strings.isNullOrEmpty(s)) {
229             lldpSuppression = s;
230         }
231         requestIntercepts();
232         loadSuppressionRules();
233     }
234
235     private void loadSuppressionRules() {
236         SuppressionRulesStore store = new SuppressionRulesStore(lldpSuppression);
237         try {
238             log.info("Reading suppression rules from {}", lldpSuppression);
239             rules = store.read();
240         } catch (IOException e) {
241             log.info("Failed to load {}, using built-in rules", lldpSuppression);
242             // default rule to suppress ROADM to maintain compatibility
243             rules = new SuppressionRules(ImmutableSet.of(),
244                                          EnumSet.of(Device.Type.ROADM),
245                                          ImmutableMap.of());
246         }
247
248         // should refresh discoverers when we need dynamic reconfiguration
249     }
250
251     /**
252      * Request packet intercepts.
253      */
254     private void requestIntercepts() {
255         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
256         selector.matchEthType(Ethernet.TYPE_LLDP);
257         packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
258
259         selector.matchEthType(Ethernet.TYPE_BSN);
260         if (useBDDP) {
261             packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
262         } else {
263             packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
264         }
265     }
266
267     /**
268      * Withdraw packet intercepts.
269      */
270     private void withdrawIntercepts() {
271         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
272         selector.matchEthType(Ethernet.TYPE_LLDP);
273         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
274         selector.matchEthType(Ethernet.TYPE_BSN);
275         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
276     }
277
278     private LinkDiscovery createLinkDiscovery(Device device) {
279         return new LinkDiscovery(device, packetService, masterService,
280                                  providerService, useBDDP);
281     }
282
283     private class InternalRoleListener implements MastershipListener {
284
285         @Override
286         public void event(MastershipEvent event) {
287             if (MastershipEvent.Type.BACKUPS_CHANGED.equals(event.type())) {
288                 // only need new master events
289                 return;
290             }
291
292             DeviceId deviceId = event.subject();
293             Device device = deviceService.getDevice(deviceId);
294             if (device == null) {
295                 log.debug("Device {} doesn't exist, or isn't there yet", deviceId);
296                 return;
297             }
298             if (rules.isSuppressed(device)) {
299                 return;
300             }
301             synchronized (discoverers) {
302                 if (!discoverers.containsKey(deviceId)) {
303                     // ideally, should never reach here
304                     log.debug("Device mastership changed ({}) {}", event.type(), deviceId);
305                     discoverers.put(deviceId, createLinkDiscovery(device));
306                 }
307             }
308         }
309
310     }
311
312     private class InternalLinkProvider implements PacketProcessor, DeviceListener {
313
314         @Override
315         public void event(DeviceEvent event) {
316             LinkDiscovery ld = null;
317             Device device = event.subject();
318             Port port = event.port();
319             if (device == null) {
320                 log.error("Device is null.");
321                 return;
322             }
323             log.trace("{} {} {}", event.type(), event.subject(), event);
324             final DeviceId deviceId = device.id();
325             switch (event.type()) {
326                 case DEVICE_ADDED:
327                 case DEVICE_UPDATED:
328                     synchronized (discoverers) {
329                         ld = discoverers.get(deviceId);
330                         if (ld == null) {
331                             if (rules.isSuppressed(device)) {
332                                 log.debug("LinkDiscovery from {} disabled by configuration", device.id());
333                                 return;
334                             }
335                             log.debug("Device added ({}) {}", event.type(), deviceId);
336                             discoverers.put(deviceId, createLinkDiscovery(device));
337                         } else {
338                             if (ld.isStopped()) {
339                                 log.debug("Device restarted ({}) {}", event.type(), deviceId);
340                                 ld.start();
341                             }
342                         }
343                     }
344                     break;
345                 case PORT_ADDED:
346                 case PORT_UPDATED:
347                     if (port.isEnabled()) {
348                         ld = discoverers.get(deviceId);
349                         if (ld == null) {
350                             return;
351                         }
352                         if (rules.isSuppressed(port)) {
353                             log.debug("LinkDiscovery from {}@{} disabled by configuration",
354                                       port.number(), device.id());
355                             return;
356                         }
357                         if (!port.number().isLogical()) {
358                             log.debug("Port added {}", port);
359                             ld.addPort(port);
360                         }
361                     } else {
362                         log.debug("Port down {}", port);
363                         ConnectPoint point = new ConnectPoint(deviceId, port.number());
364                         providerService.linksVanished(point);
365                     }
366                     break;
367                 case PORT_REMOVED:
368                     log.debug("Port removed {}", port);
369                     ConnectPoint point = new ConnectPoint(deviceId, port.number());
370                     providerService.linksVanished(point);
371
372                     break;
373                 case DEVICE_REMOVED:
374                 case DEVICE_SUSPENDED:
375                     log.debug("Device removed {}", deviceId);
376                     ld = discoverers.get(deviceId);
377                     if (ld == null) {
378                         return;
379                     }
380                     ld.stop();
381                     providerService.linksVanished(deviceId);
382                     break;
383                 case DEVICE_AVAILABILITY_CHANGED:
384                     ld = discoverers.get(deviceId);
385                     if (ld == null) {
386                         return;
387                     }
388                     if (deviceService.isAvailable(deviceId)) {
389                         log.debug("Device up {}", deviceId);
390                         ld.start();
391                     } else {
392                         providerService.linksVanished(deviceId);
393                         log.debug("Device down {}", deviceId);
394                         ld.stop();
395                     }
396                     break;
397                 case PORT_STATS_UPDATED:
398                     break;
399                 default:
400                     log.debug("Unknown event {}", event);
401             }
402         }
403
404         @Override
405         public void process(PacketContext context) {
406             if (context == null) {
407                 return;
408             }
409             LinkDiscovery ld = discoverers.get(context.inPacket().receivedFrom().deviceId());
410             if (ld == null) {
411                 return;
412             }
413
414             if (ld.handleLLDP(context)) {
415                 context.block();
416             }
417         }
418     }
419
420     private final class SyncDeviceInfoTask implements Runnable {
421
422         @Override
423         public void run() {
424             if (Thread.currentThread().isInterrupted()) {
425                 log.info("Interrupted, quitting");
426                 return;
427             }
428             // check what deviceService sees, to see if we are missing anything
429             try {
430                 for (Device dev : deviceService.getDevices()) {
431                     if (rules.isSuppressed(dev)) {
432                         continue;
433                     }
434                     DeviceId did = dev.id();
435                     synchronized (discoverers) {
436                         LinkDiscovery discoverer = discoverers.get(did);
437                         if (discoverer == null) {
438                             discoverer = createLinkDiscovery(dev);
439                             discoverers.put(did, discoverer);
440                         }
441
442                         addPorts(discoverer, did);
443                     }
444                 }
445             } catch (Exception e) {
446                 // catch all Exception to avoid Scheduled task being suppressed.
447                 log.error("Exception thrown during synchronization process", e);
448             }
449         }
450     }
451
452 }