cb19dc52b3b4e72702d6f0a21dda4b05865246ef
[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.of.device.impl;
17
18 import com.google.common.base.Strings;
19 import com.google.common.collect.Lists;
20 import com.google.common.collect.Maps;
21 import com.google.common.collect.Sets;
22 import org.apache.felix.scr.annotations.Activate;
23 import org.apache.felix.scr.annotations.Component;
24 import org.apache.felix.scr.annotations.Deactivate;
25 import org.apache.felix.scr.annotations.Modified;
26 import org.apache.felix.scr.annotations.Property;
27 import org.apache.felix.scr.annotations.Reference;
28 import org.apache.felix.scr.annotations.ReferenceCardinality;
29 import org.onlab.packet.ChassisId;
30 import org.onlab.util.Frequency;
31 import org.onosproject.cfg.ComponentConfigService;
32 import org.onlab.util.Spectrum;
33 import org.onosproject.net.AnnotationKeys;
34 import org.onosproject.net.ChannelSpacing;
35 import org.onosproject.net.DefaultAnnotations;
36 import org.onosproject.net.DeviceId;
37 import org.onosproject.net.GridType;
38 import org.onosproject.net.MastershipRole;
39 import org.onosproject.net.OchSignal;
40 import org.onosproject.net.OduSignalType;
41 import org.onosproject.net.Port;
42 import org.onosproject.net.PortNumber;
43 import org.onosproject.net.SparseAnnotations;
44 import org.onosproject.net.device.DefaultDeviceDescription;
45 import org.onosproject.net.device.DefaultPortDescription;
46 import org.onosproject.net.device.DefaultPortStatistics;
47 import org.onosproject.net.device.DeviceDescription;
48 import org.onosproject.net.device.DeviceProvider;
49 import org.onosproject.net.device.DeviceProviderRegistry;
50 import org.onosproject.net.device.DeviceProviderService;
51 import org.onosproject.net.device.OchPortDescription;
52 import org.onosproject.net.device.OmsPortDescription;
53 import org.onosproject.net.device.PortDescription;
54 import org.onosproject.net.device.PortStatistics;
55 import org.onosproject.net.provider.AbstractProvider;
56 import org.onosproject.net.provider.ProviderId;
57 import org.onosproject.openflow.controller.Dpid;
58 import org.onosproject.openflow.controller.OpenFlowController;
59 import org.onosproject.openflow.controller.OpenFlowEventListener;
60 import org.onosproject.openflow.controller.OpenFlowOpticalSwitch;
61 import org.onosproject.openflow.controller.OpenFlowSwitch;
62 import org.onosproject.openflow.controller.OpenFlowSwitchListener;
63 import org.onosproject.openflow.controller.PortDescPropertyType;
64 import org.onosproject.openflow.controller.RoleState;
65 import org.osgi.service.component.ComponentContext;
66 import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry;
67 import org.projectfloodlight.openflow.protocol.OFFactory;
68 import org.projectfloodlight.openflow.protocol.OFMessage;
69 import org.projectfloodlight.openflow.protocol.OFPortConfig;
70 import org.projectfloodlight.openflow.protocol.OFPortDesc;
71 import org.projectfloodlight.openflow.protocol.OFPortDescPropOpticalTransport;
72 import org.projectfloodlight.openflow.protocol.OFPortFeatures;
73 import org.projectfloodlight.openflow.protocol.OFPortOptical;
74 import org.projectfloodlight.openflow.protocol.OFPortReason;
75 import org.projectfloodlight.openflow.protocol.OFPortState;
76 import org.projectfloodlight.openflow.protocol.OFPortStatsEntry;
77 import org.projectfloodlight.openflow.protocol.OFPortStatsReply;
78 import org.projectfloodlight.openflow.protocol.OFPortStatus;
79 import org.projectfloodlight.openflow.protocol.OFStatsReply;
80 import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
81 import org.projectfloodlight.openflow.protocol.OFStatsType;
82 import org.projectfloodlight.openflow.protocol.OFVersion;
83 import org.projectfloodlight.openflow.types.PortSpeed;
84 import org.slf4j.Logger;
85
86 import java.util.ArrayList;
87 import java.util.Collection;
88 import java.util.Collections;
89 import java.util.Dictionary;
90 import java.util.HashMap;
91 import java.util.HashSet;
92 import java.util.List;
93
94 import static com.google.common.base.Preconditions.checkArgument;
95 import static com.google.common.base.Strings.isNullOrEmpty;
96 import static org.onlab.util.Tools.get;
97 import static org.onosproject.net.DeviceId.deviceId;
98 import static org.onosproject.net.Port.Type.COPPER;
99 import static org.onosproject.net.Port.Type.FIBER;
100 import static org.onosproject.openflow.controller.Dpid.dpid;
101 import static org.onosproject.openflow.controller.Dpid.uri;
102 import static org.slf4j.LoggerFactory.getLogger;
103
104 /**
105  * Provider which uses an OpenFlow controller to detect network
106  * infrastructure devices.
107  */
108 @Component(immediate = true)
109 public class OpenFlowDeviceProvider extends AbstractProvider implements DeviceProvider {
110
111     private static final Logger LOG = getLogger(OpenFlowDeviceProvider.class);
112     private static final long MBPS = 1_000 * 1_000;
113
114     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115     protected DeviceProviderRegistry providerRegistry;
116
117     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118     protected OpenFlowController controller;
119
120     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121     protected ComponentConfigService cfgService;
122
123     private DeviceProviderService providerService;
124
125     private final InternalDeviceProvider listener = new InternalDeviceProvider();
126
127     // TODO: We need to make the poll interval configurable.
128     static final int POLL_INTERVAL = 5;
129     @Property(name = "PortStatsPollFrequency", intValue = POLL_INTERVAL,
130     label = "Frequency (in seconds) for polling switch Port statistics")
131     private int portStatsPollFrequency = POLL_INTERVAL;
132
133     private HashMap<Dpid, PortStatsCollector> collectors = Maps.newHashMap();
134
135     /**
136      * Creates an OpenFlow device provider.
137      */
138     public OpenFlowDeviceProvider() {
139         super(new ProviderId("of", "org.onosproject.provider.openflow"));
140     }
141
142     @Activate
143     public void activate(ComponentContext context) {
144         cfgService.registerProperties(getClass());
145         providerService = providerRegistry.register(this);
146         controller.addListener(listener);
147         controller.addEventListener(listener);
148         for (OpenFlowSwitch sw : controller.getSwitches()) {
149             try {
150                 listener.switchAdded(new Dpid(sw.getId()));
151             } catch (Exception e) {
152                 LOG.warn("Failed initially adding {} : {}", sw.getStringId(), e.getMessage());
153                 LOG.debug("Error details:", e);
154                 // disconnect to trigger switch-add later
155                 sw.disconnectSwitch();
156             }
157             PortStatsCollector psc = new PortStatsCollector(sw, portStatsPollFrequency);
158             psc.start();
159             collectors.put(new Dpid(sw.getId()), psc);
160         }
161         LOG.info("Started");
162     }
163
164     @Deactivate
165     public void deactivate(ComponentContext context) {
166         cfgService.unregisterProperties(getClass(), false);
167         providerRegistry.unregister(this);
168         controller.removeListener(listener);
169         collectors.values().forEach(PortStatsCollector::stop);
170         providerService = null;
171         LOG.info("Stopped");
172     }
173
174     @Modified
175     public void modified(ComponentContext context) {
176         Dictionary<?, ?> properties = context.getProperties();
177         int newPortStatsPollFrequency;
178         try {
179             String s = get(properties, "PortStatsPollFrequency");
180             newPortStatsPollFrequency = isNullOrEmpty(s) ? portStatsPollFrequency : Integer.parseInt(s.trim());
181
182         } catch (NumberFormatException | ClassCastException e) {
183             newPortStatsPollFrequency = portStatsPollFrequency;
184         }
185
186         if (newPortStatsPollFrequency != portStatsPollFrequency) {
187             portStatsPollFrequency = newPortStatsPollFrequency;
188             collectors.values().forEach(psc -> psc.adjustPollInterval(portStatsPollFrequency));
189         }
190
191         LOG.info("Settings: portStatsPollFrequency={}", portStatsPollFrequency);
192     }
193
194     @Override
195     public boolean isReachable(DeviceId deviceId) {
196         OpenFlowSwitch sw = controller.getSwitch(dpid(deviceId.uri()));
197         if (sw == null || !sw.isConnected()) {
198             return false;
199         }
200         return true;
201     }
202
203     @Override
204     public void triggerProbe(DeviceId deviceId) {
205         LOG.debug("Triggering probe on device {}", deviceId);
206
207         final Dpid dpid = dpid(deviceId.uri());
208         OpenFlowSwitch sw = controller.getSwitch(dpid);
209         if (sw == null || !sw.isConnected()) {
210             LOG.error("Failed to probe device {} on sw={}", deviceId, sw);
211             providerService.deviceDisconnected(deviceId);
212             return;
213         } else {
214             LOG.trace("Confirmed device {} connection", deviceId);
215         }
216
217         // Prompt an update of port information. We can use any XID for this.
218         OFFactory fact = sw.factory();
219         switch (fact.getVersion()) {
220             case OF_10:
221                 sw.sendMsg(fact.buildFeaturesRequest().setXid(0).build());
222                 break;
223             case OF_13:
224                 sw.sendMsg(fact.buildPortDescStatsRequest().setXid(0).build());
225                 break;
226             default:
227                 LOG.warn("Unhandled protocol version");
228         }
229     }
230
231     @Override
232     public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
233         switch (newRole) {
234             case MASTER:
235                 controller.setRole(dpid(deviceId.uri()), RoleState.MASTER);
236                 break;
237             case STANDBY:
238                 controller.setRole(dpid(deviceId.uri()), RoleState.EQUAL);
239                 break;
240             case NONE:
241                 controller.setRole(dpid(deviceId.uri()), RoleState.SLAVE);
242                 break;
243             default:
244                 LOG.error("Unknown Mastership state : {}", newRole);
245
246         }
247         LOG.debug("Accepting mastership role change for device {}", deviceId);
248     }
249
250     private void pushPortMetrics(Dpid dpid, List<OFPortStatsEntry> portStatsEntries) {
251         DeviceId deviceId = DeviceId.deviceId(dpid.uri(dpid));
252         Collection<PortStatistics> stats = buildPortStatistics(deviceId, portStatsEntries);
253         providerService.updatePortStatistics(deviceId, stats);
254     }
255
256     private Collection<PortStatistics> buildPortStatistics(DeviceId deviceId,
257                                                            List<OFPortStatsEntry> entries) {
258         HashSet<PortStatistics> stats = Sets.newHashSet();
259
260         for (OFPortStatsEntry entry : entries) {
261             try {
262                 if (entry == null || entry.getPortNo() == null || entry.getPortNo().getPortNumber() < 0) {
263                     continue;
264                 }
265                 DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder();
266                 DefaultPortStatistics stat = builder.setDeviceId(deviceId)
267                         .setPort(entry.getPortNo().getPortNumber())
268                         .setPacketsReceived(entry.getRxPackets().getValue())
269                         .setPacketsSent(entry.getTxPackets().getValue())
270                         .setBytesReceived(entry.getRxBytes().getValue())
271                         .setBytesSent(entry.getTxBytes().getValue())
272                         .setPacketsRxDropped(entry.getRxDropped().getValue())
273                         .setPacketsTxDropped(entry.getTxDropped().getValue())
274                         .setPacketsRxErrors(entry.getRxErrors().getValue())
275                         .setPacketsTxErrors(entry.getTxErrors().getValue())
276                         .setDurationSec(entry.getVersion() == OFVersion.OF_10 ? 0 : entry.getDurationSec())
277                         .setDurationNano(entry.getVersion() == OFVersion.OF_10 ? 0 : entry.getDurationNsec())
278                         .build();
279
280                 stats.add(stat);
281             } catch (Exception e) {
282                 LOG.warn("Unable to process port stats", e);
283             }
284         }
285
286         return Collections.unmodifiableSet(stats);
287
288     }
289
290     private class InternalDeviceProvider implements OpenFlowSwitchListener, OpenFlowEventListener {
291
292         private HashMap<Dpid, List<OFPortStatsEntry>> portStatsReplies = new HashMap<>();
293
294         @Override
295         public void switchAdded(Dpid dpid) {
296             if (providerService == null) {
297                 return;
298             }
299             DeviceId did = deviceId(uri(dpid));
300             OpenFlowSwitch sw = controller.getSwitch(dpid);
301
302             ChassisId cId = new ChassisId(dpid.value());
303
304             SparseAnnotations annotations = DefaultAnnotations.builder()
305                     .set("protocol", sw.factory().getVersion().toString())
306                     .set("channelId", sw.channelId())
307                     .build();
308
309             DeviceDescription description =
310                     new DefaultDeviceDescription(did.uri(), sw.deviceType(),
311                                                  sw.manufacturerDescription(),
312                                                  sw.hardwareDescription(),
313                                                  sw.softwareDescription(),
314                                                  sw.serialNumber(),
315                                                  cId, annotations);
316             providerService.deviceConnected(did, description);
317             providerService.updatePorts(did, buildPortDescriptions(sw));
318
319             PortStatsCollector psc =
320                     new PortStatsCollector(controller.getSwitch(dpid), portStatsPollFrequency);
321             psc.start();
322             collectors.put(dpid, psc);
323         }
324
325         @Override
326         public void switchRemoved(Dpid dpid) {
327             if (providerService == null) {
328                 return;
329             }
330             providerService.deviceDisconnected(deviceId(uri(dpid)));
331
332             PortStatsCollector collector = collectors.remove(dpid);
333             if (collector != null) {
334                 collector.stop();
335             }
336         }
337
338         @Override
339         public void switchChanged(Dpid dpid) {
340             if (providerService == null) {
341                 return;
342             }
343             DeviceId did = deviceId(uri(dpid));
344             OpenFlowSwitch sw = controller.getSwitch(dpid);
345             providerService.updatePorts(did, buildPortDescriptions(sw));
346         }
347
348         @Override
349         public void portChanged(Dpid dpid, OFPortStatus status) {
350             PortDescription portDescription = buildPortDescription(status);
351             providerService.portStatusChanged(deviceId(uri(dpid)), portDescription);
352         }
353
354         @Override
355         public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) {
356             MastershipRole request = roleOf(requested);
357             MastershipRole reply = roleOf(response);
358
359             providerService.receivedRoleReply(deviceId(uri(dpid)), request, reply);
360         }
361
362         /**
363          * Translates a RoleState to the corresponding MastershipRole.
364          *
365          * @param response role state
366          * @return a MastershipRole
367          */
368         private MastershipRole roleOf(RoleState response) {
369             switch (response) {
370                 case MASTER:
371                     return MastershipRole.MASTER;
372                 case EQUAL:
373                     return MastershipRole.STANDBY;
374                 case SLAVE:
375                     return MastershipRole.NONE;
376                 default:
377                     LOG.warn("unknown role {}", response);
378                     return null;
379             }
380         }
381
382         /**
383          * Builds a list of port descriptions for a given list of ports.
384          *
385          * @return list of portdescriptions
386          */
387         private List<PortDescription> buildPortDescriptions(OpenFlowSwitch sw) {
388             final List<PortDescription> portDescs = new ArrayList<>(sw.getPorts().size());
389             sw.getPorts().forEach(port -> portDescs.add(buildPortDescription(port)));
390
391             OpenFlowOpticalSwitch opsw;
392             switch (sw.deviceType()) {
393                 case ROADM:
394                     opsw = (OpenFlowOpticalSwitch) sw;
395                     opsw.getPortTypes().forEach(type -> {
396                         opsw.getPortsOf(type).forEach(
397                                 op -> {
398                                     portDescs.add(buildPortDescription(type, (OFPortOptical) op));
399                                 }
400                         );
401                     });
402                     break;
403                 case FIBER_SWITCH:
404                     opsw = (OpenFlowOpticalSwitch) sw;
405                     opsw.getPortTypes().forEach(type -> {
406                         opsw.getPortsOf(type).forEach(
407                                 op -> {
408                                     portDescs.add(buildPortDescription((OFCalientPortDescStatsEntry) op));
409                                 }
410                         );
411                     });
412                     break;
413                 default:
414                     break;
415             }
416
417             return portDescs;
418         }
419
420         /**
421          * Creates an annotation for the port name if one is available.
422          *
423          * @param port description of the port
424          * @return annotation containing the port name if one is found,
425          *         null otherwise
426          */
427         private SparseAnnotations makePortNameAnnotation(String port) {
428             SparseAnnotations annotations = null;
429             String portName = Strings.emptyToNull(port);
430             if (portName != null) {
431                 annotations = DefaultAnnotations.builder()
432                         .set(AnnotationKeys.PORT_NAME, portName).build();
433             }
434             return annotations;
435         }
436
437         /**
438          * Build a portDescription from a given Ethernet port description.
439          *
440          * @param port the port to build from.
441          * @return portDescription for the port.
442          */
443         private PortDescription buildPortDescription(OFPortDesc port) {
444             PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
445             boolean enabled =
446                     !port.getState().contains(OFPortState.LINK_DOWN) &&
447                             !port.getConfig().contains(OFPortConfig.PORT_DOWN);
448             Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER;
449             SparseAnnotations annotations = makePortNameAnnotation(port.getName());
450             return new DefaultPortDescription(portNo, enabled, type,
451                                               portSpeed(port), annotations);
452         }
453
454         /**
455          * Build a portDescription from a given a port description describing some
456          * Optical port.
457          *
458          * @param port description property type.
459          * @param port the port to build from.
460          * @return portDescription for the port.
461          */
462         private PortDescription buildPortDescription(PortDescPropertyType ptype, OFPortOptical port) {
463             checkArgument(port.getDesc().size() >= 1);
464
465             // Minimally functional fixture. This needs to be fixed as we add better support.
466             PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
467
468             boolean enabled = !port.getState().contains(OFPortState.LINK_DOWN)
469                     && !port.getConfig().contains(OFPortConfig.PORT_DOWN);
470             SparseAnnotations annotations = makePortNameAnnotation(port.getName());
471
472             if (port.getVersion() == OFVersion.OF_13
473                     && ptype == PortDescPropertyType.OPTICAL_TRANSPORT) {
474                 // At this point, not much is carried in the optical port message.
475                 LOG.debug("Optical transport port message {}", port.toString());
476             } else {
477                 // removable once 1.4+ support complete.
478                 LOG.debug("Unsupported optical port properties");
479             }
480
481             OFPortDescPropOpticalTransport desc = port.getDesc().get(0);
482             switch (desc.getPortSignalType()) {
483                 // FIXME: use constants once loxi has full optical extensions
484                 case 2:     // OMS port
485                     // Assume complete optical spectrum and 50 GHz grid
486                     // LINC-OE is only supported optical OF device for now
487                     return new OmsPortDescription(portNo, enabled,
488                             Spectrum.U_BAND_MIN, Spectrum.O_BAND_MAX, Frequency.ofGHz(50), annotations);
489                 case 5:     // OCH port
490                     OchSignal signal = new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, 0, 4);
491                     return new OchPortDescription(portNo, enabled, OduSignalType.ODU4,
492                             true, signal, annotations);
493                 default:
494                     break;
495             }
496
497             return new DefaultPortDescription(portNo, enabled, FIBER, 0, annotations);
498         }
499
500         /**
501          * Build a portDescription from a given port description describing a fiber switch optical port.
502          *
503          * @param port description property type.
504          * @param port the port to build from.
505          * @return portDescription for the port.
506          */
507         private PortDescription buildPortDescription(OFCalientPortDescStatsEntry port) {
508             PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
509
510             // FIXME when Calient OF agent reports port status
511             boolean enabled = true;
512             SparseAnnotations annotations = makePortNameAnnotation(port.getName());
513
514             // S160 data sheet
515             // Wavelength range: 1260 - 1630 nm, grid is irrelevant for this type of switch
516             return new OmsPortDescription(portNo, enabled,
517                     Spectrum.U_BAND_MIN, Spectrum.O_BAND_MAX, Frequency.ofGHz(100), annotations);
518         }
519
520         private PortDescription buildPortDescription(OFPortStatus status) {
521             OFPortDesc port = status.getDesc();
522             if (status.getReason() != OFPortReason.DELETE) {
523                 return buildPortDescription(port);
524             } else {
525                 PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
526                 Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER;
527                 SparseAnnotations annotations = makePortNameAnnotation(port.getName());
528                 return new DefaultPortDescription(portNo, false, type,
529                                                   portSpeed(port), annotations);
530             }
531         }
532
533         private long portSpeed(OFPortDesc port) {
534             if (port.getVersion() == OFVersion.OF_13) {
535                 return port.getCurrSpeed() / MBPS;
536             }
537
538             PortSpeed portSpeed = PortSpeed.SPEED_NONE;
539             for (OFPortFeatures feat : port.getCurr()) {
540                 portSpeed = PortSpeed.max(portSpeed, feat.getPortSpeed());
541             }
542             return portSpeed.getSpeedBps() / MBPS;
543         }
544
545         @Override
546         public void handleMessage(Dpid dpid, OFMessage msg) {
547             switch (msg.getType()) {
548                 case STATS_REPLY:
549                     if (((OFStatsReply) msg).getStatsType() == OFStatsType.PORT) {
550                         OFPortStatsReply portStatsReply = (OFPortStatsReply) msg;
551                         List<OFPortStatsEntry> portStatsReplyList = portStatsReplies.get(dpid);
552                         if (portStatsReplyList == null) {
553                             portStatsReplyList = Lists.newArrayList();
554                         }
555                         portStatsReplyList.addAll(portStatsReply.getEntries());
556                         portStatsReplies.put(dpid, portStatsReplyList);
557                         if (!portStatsReply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
558                             pushPortMetrics(dpid, portStatsReplies.get(dpid));
559                             portStatsReplies.get(dpid).clear();
560                         }
561                     }
562                     break;
563                 default:
564                     break;
565             }
566         }
567     }
568
569 }