2 * Copyright 2014-2015 Open Networking Laboratory
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.onosproject.provider.of.device.impl;
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;
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;
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;
105 * Provider which uses an OpenFlow controller to detect network
106 * infrastructure devices.
108 @Component(immediate = true)
109 public class OpenFlowDeviceProvider extends AbstractProvider implements DeviceProvider {
111 private static final Logger LOG = getLogger(OpenFlowDeviceProvider.class);
112 private static final long MBPS = 1_000 * 1_000;
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 protected DeviceProviderRegistry providerRegistry;
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected OpenFlowController controller;
120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 protected ComponentConfigService cfgService;
123 private DeviceProviderService providerService;
125 private final InternalDeviceProvider listener = new InternalDeviceProvider();
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;
133 private HashMap<Dpid, PortStatsCollector> collectors = Maps.newHashMap();
136 * Creates an OpenFlow device provider.
138 public OpenFlowDeviceProvider() {
139 super(new ProviderId("of", "org.onosproject.provider.openflow"));
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()) {
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();
157 PortStatsCollector psc = new PortStatsCollector(sw, portStatsPollFrequency);
159 collectors.put(new Dpid(sw.getId()), psc);
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;
175 public void modified(ComponentContext context) {
176 Dictionary<?, ?> properties = context.getProperties();
177 int newPortStatsPollFrequency;
179 String s = get(properties, "PortStatsPollFrequency");
180 newPortStatsPollFrequency = isNullOrEmpty(s) ? portStatsPollFrequency : Integer.parseInt(s.trim());
182 } catch (NumberFormatException | ClassCastException e) {
183 newPortStatsPollFrequency = portStatsPollFrequency;
186 if (newPortStatsPollFrequency != portStatsPollFrequency) {
187 portStatsPollFrequency = newPortStatsPollFrequency;
188 collectors.values().forEach(psc -> psc.adjustPollInterval(portStatsPollFrequency));
191 LOG.info("Settings: portStatsPollFrequency={}", portStatsPollFrequency);
195 public boolean isReachable(DeviceId deviceId) {
196 OpenFlowSwitch sw = controller.getSwitch(dpid(deviceId.uri()));
197 if (sw == null || !sw.isConnected()) {
204 public void triggerProbe(DeviceId deviceId) {
205 LOG.debug("Triggering probe on device {}", deviceId);
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);
214 LOG.trace("Confirmed device {} connection", deviceId);
217 // Prompt an update of port information. We can use any XID for this.
218 OFFactory fact = sw.factory();
219 switch (fact.getVersion()) {
221 sw.sendMsg(fact.buildFeaturesRequest().setXid(0).build());
224 sw.sendMsg(fact.buildPortDescStatsRequest().setXid(0).build());
227 LOG.warn("Unhandled protocol version");
232 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
235 controller.setRole(dpid(deviceId.uri()), RoleState.MASTER);
238 controller.setRole(dpid(deviceId.uri()), RoleState.EQUAL);
241 controller.setRole(dpid(deviceId.uri()), RoleState.SLAVE);
244 LOG.error("Unknown Mastership state : {}", newRole);
247 LOG.debug("Accepting mastership role change for device {}", deviceId);
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);
256 private Collection<PortStatistics> buildPortStatistics(DeviceId deviceId,
257 List<OFPortStatsEntry> entries) {
258 HashSet<PortStatistics> stats = Sets.newHashSet();
260 for (OFPortStatsEntry entry : entries) {
262 if (entry == null || entry.getPortNo() == null || entry.getPortNo().getPortNumber() < 0) {
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())
281 } catch (Exception e) {
282 LOG.warn("Unable to process port stats", e);
286 return Collections.unmodifiableSet(stats);
290 private class InternalDeviceProvider implements OpenFlowSwitchListener, OpenFlowEventListener {
292 private HashMap<Dpid, List<OFPortStatsEntry>> portStatsReplies = new HashMap<>();
295 public void switchAdded(Dpid dpid) {
296 if (providerService == null) {
299 DeviceId did = deviceId(uri(dpid));
300 OpenFlowSwitch sw = controller.getSwitch(dpid);
302 ChassisId cId = new ChassisId(dpid.value());
304 SparseAnnotations annotations = DefaultAnnotations.builder()
305 .set("protocol", sw.factory().getVersion().toString())
306 .set("channelId", sw.channelId())
309 DeviceDescription description =
310 new DefaultDeviceDescription(did.uri(), sw.deviceType(),
311 sw.manufacturerDescription(),
312 sw.hardwareDescription(),
313 sw.softwareDescription(),
316 providerService.deviceConnected(did, description);
317 providerService.updatePorts(did, buildPortDescriptions(sw));
319 PortStatsCollector psc =
320 new PortStatsCollector(controller.getSwitch(dpid), portStatsPollFrequency);
322 collectors.put(dpid, psc);
326 public void switchRemoved(Dpid dpid) {
327 if (providerService == null) {
330 providerService.deviceDisconnected(deviceId(uri(dpid)));
332 PortStatsCollector collector = collectors.remove(dpid);
333 if (collector != null) {
339 public void switchChanged(Dpid dpid) {
340 if (providerService == null) {
343 DeviceId did = deviceId(uri(dpid));
344 OpenFlowSwitch sw = controller.getSwitch(dpid);
345 providerService.updatePorts(did, buildPortDescriptions(sw));
349 public void portChanged(Dpid dpid, OFPortStatus status) {
350 PortDescription portDescription = buildPortDescription(status);
351 providerService.portStatusChanged(deviceId(uri(dpid)), portDescription);
355 public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) {
356 MastershipRole request = roleOf(requested);
357 MastershipRole reply = roleOf(response);
359 providerService.receivedRoleReply(deviceId(uri(dpid)), request, reply);
363 * Translates a RoleState to the corresponding MastershipRole.
365 * @param response role state
366 * @return a MastershipRole
368 private MastershipRole roleOf(RoleState response) {
371 return MastershipRole.MASTER;
373 return MastershipRole.STANDBY;
375 return MastershipRole.NONE;
377 LOG.warn("unknown role {}", response);
383 * Builds a list of port descriptions for a given list of ports.
385 * @return list of portdescriptions
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)));
391 OpenFlowOpticalSwitch opsw;
392 switch (sw.deviceType()) {
394 opsw = (OpenFlowOpticalSwitch) sw;
395 opsw.getPortTypes().forEach(type -> {
396 opsw.getPortsOf(type).forEach(
398 portDescs.add(buildPortDescription(type, (OFPortOptical) op));
404 opsw = (OpenFlowOpticalSwitch) sw;
405 opsw.getPortTypes().forEach(type -> {
406 opsw.getPortsOf(type).forEach(
408 portDescs.add(buildPortDescription((OFCalientPortDescStatsEntry) op));
421 * Creates an annotation for the port name if one is available.
423 * @param port description of the port
424 * @return annotation containing the port name if one is found,
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();
438 * Build a portDescription from a given Ethernet port description.
440 * @param port the port to build from.
441 * @return portDescription for the port.
443 private PortDescription buildPortDescription(OFPortDesc port) {
444 PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
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);
455 * Build a portDescription from a given a port description describing some
458 * @param port description property type.
459 * @param port the port to build from.
460 * @return portDescription for the port.
462 private PortDescription buildPortDescription(PortDescPropertyType ptype, OFPortOptical port) {
463 checkArgument(port.getDesc().size() >= 1);
465 // Minimally functional fixture. This needs to be fixed as we add better support.
466 PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
468 boolean enabled = !port.getState().contains(OFPortState.LINK_DOWN)
469 && !port.getConfig().contains(OFPortConfig.PORT_DOWN);
470 SparseAnnotations annotations = makePortNameAnnotation(port.getName());
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());
477 // removable once 1.4+ support complete.
478 LOG.debug("Unsupported optical port properties");
481 OFPortDescPropOpticalTransport desc = port.getDesc().get(0);
482 switch (desc.getPortSignalType()) {
483 // FIXME: use constants once loxi has full optical extensions
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);
490 OchSignal signal = new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, 0, 4);
491 return new OchPortDescription(portNo, enabled, OduSignalType.ODU4,
492 true, signal, annotations);
497 return new DefaultPortDescription(portNo, enabled, FIBER, 0, annotations);
501 * Build a portDescription from a given port description describing a fiber switch optical port.
503 * @param port description property type.
504 * @param port the port to build from.
505 * @return portDescription for the port.
507 private PortDescription buildPortDescription(OFCalientPortDescStatsEntry port) {
508 PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
510 // FIXME when Calient OF agent reports port status
511 boolean enabled = true;
512 SparseAnnotations annotations = makePortNameAnnotation(port.getName());
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);
520 private PortDescription buildPortDescription(OFPortStatus status) {
521 OFPortDesc port = status.getDesc();
522 if (status.getReason() != OFPortReason.DELETE) {
523 return buildPortDescription(port);
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);
533 private long portSpeed(OFPortDesc port) {
534 if (port.getVersion() == OFVersion.OF_13) {
535 return port.getCurrSpeed() / MBPS;
538 PortSpeed portSpeed = PortSpeed.SPEED_NONE;
539 for (OFPortFeatures feat : port.getCurr()) {
540 portSpeed = PortSpeed.max(portSpeed, feat.getPortSpeed());
542 return portSpeed.getSpeedBps() / MBPS;
546 public void handleMessage(Dpid dpid, OFMessage msg) {
547 switch (msg.getType()) {
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();
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();