diff options
Diffstat (limited to 'framework/src/onos/providers/openflow/device')
5 files changed, 1143 insertions, 0 deletions
diff --git a/framework/src/onos/providers/openflow/device/pom.xml b/framework/src/onos/providers/openflow/device/pom.xml new file mode 100644 index 00000000..5f27d42b --- /dev/null +++ b/framework/src/onos/providers/openflow/device/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-provider-device</artifactId> + <packaging>bundle</packaging> + + <description>ONOS OpenFlow protocol device provider</description> + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + </dependencies> +</project> diff --git a/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java new file mode 100644 index 00000000..cb19dc52 --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java @@ -0,0 +1,569 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.device.impl; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.ChassisId; +import org.onlab.util.Frequency; +import org.onosproject.cfg.ComponentConfigService; +import org.onlab.util.Spectrum; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.GridType; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.OchSignal; +import org.onosproject.net.OduSignalType; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DefaultPortStatistics; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.OchPortDescription; +import org.onosproject.net.device.OmsPortDescription; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.PortDescPropertyType; +import org.onosproject.openflow.controller.RoleState; +import org.osgi.service.component.ComponentContext; +import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortConfig; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescPropOpticalTransport; +import org.projectfloodlight.openflow.protocol.OFPortFeatures; +import org.projectfloodlight.openflow.protocol.OFPortOptical; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortState; +import org.projectfloodlight.openflow.protocol.OFPortStatsEntry; +import org.projectfloodlight.openflow.protocol.OFPortStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.PortSpeed; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onlab.util.Tools.get; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.Port.Type.COPPER; +import static org.onosproject.net.Port.Type.FIBER; +import static org.onosproject.openflow.controller.Dpid.dpid; +import static org.onosproject.openflow.controller.Dpid.uri; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to detect network + * infrastructure devices. + */ +@Component(immediate = true) +public class OpenFlowDeviceProvider extends AbstractProvider implements DeviceProvider { + + private static final Logger LOG = getLogger(OpenFlowDeviceProvider.class); + private static final long MBPS = 1_000 * 1_000; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenFlowController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private DeviceProviderService providerService; + + private final InternalDeviceProvider listener = new InternalDeviceProvider(); + + // TODO: We need to make the poll interval configurable. + static final int POLL_INTERVAL = 5; + @Property(name = "PortStatsPollFrequency", intValue = POLL_INTERVAL, + label = "Frequency (in seconds) for polling switch Port statistics") + private int portStatsPollFrequency = POLL_INTERVAL; + + private HashMap<Dpid, PortStatsCollector> collectors = Maps.newHashMap(); + + /** + * Creates an OpenFlow device provider. + */ + public OpenFlowDeviceProvider() { + super(new ProviderId("of", "org.onosproject.provider.openflow")); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + providerService = providerRegistry.register(this); + controller.addListener(listener); + controller.addEventListener(listener); + for (OpenFlowSwitch sw : controller.getSwitches()) { + try { + listener.switchAdded(new Dpid(sw.getId())); + } catch (Exception e) { + LOG.warn("Failed initially adding {} : {}", sw.getStringId(), e.getMessage()); + LOG.debug("Error details:", e); + // disconnect to trigger switch-add later + sw.disconnectSwitch(); + } + PortStatsCollector psc = new PortStatsCollector(sw, portStatsPollFrequency); + psc.start(); + collectors.put(new Dpid(sw.getId()), psc); + } + LOG.info("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + cfgService.unregisterProperties(getClass(), false); + providerRegistry.unregister(this); + controller.removeListener(listener); + collectors.values().forEach(PortStatsCollector::stop); + providerService = null; + LOG.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + int newPortStatsPollFrequency; + try { + String s = get(properties, "PortStatsPollFrequency"); + newPortStatsPollFrequency = isNullOrEmpty(s) ? portStatsPollFrequency : Integer.parseInt(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + newPortStatsPollFrequency = portStatsPollFrequency; + } + + if (newPortStatsPollFrequency != portStatsPollFrequency) { + portStatsPollFrequency = newPortStatsPollFrequency; + collectors.values().forEach(psc -> psc.adjustPollInterval(portStatsPollFrequency)); + } + + LOG.info("Settings: portStatsPollFrequency={}", portStatsPollFrequency); + } + + @Override + public boolean isReachable(DeviceId deviceId) { + OpenFlowSwitch sw = controller.getSwitch(dpid(deviceId.uri())); + if (sw == null || !sw.isConnected()) { + return false; + } + return true; + } + + @Override + public void triggerProbe(DeviceId deviceId) { + LOG.debug("Triggering probe on device {}", deviceId); + + final Dpid dpid = dpid(deviceId.uri()); + OpenFlowSwitch sw = controller.getSwitch(dpid); + if (sw == null || !sw.isConnected()) { + LOG.error("Failed to probe device {} on sw={}", deviceId, sw); + providerService.deviceDisconnected(deviceId); + return; + } else { + LOG.trace("Confirmed device {} connection", deviceId); + } + + // Prompt an update of port information. We can use any XID for this. + OFFactory fact = sw.factory(); + switch (fact.getVersion()) { + case OF_10: + sw.sendMsg(fact.buildFeaturesRequest().setXid(0).build()); + break; + case OF_13: + sw.sendMsg(fact.buildPortDescStatsRequest().setXid(0).build()); + break; + default: + LOG.warn("Unhandled protocol version"); + } + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + switch (newRole) { + case MASTER: + controller.setRole(dpid(deviceId.uri()), RoleState.MASTER); + break; + case STANDBY: + controller.setRole(dpid(deviceId.uri()), RoleState.EQUAL); + break; + case NONE: + controller.setRole(dpid(deviceId.uri()), RoleState.SLAVE); + break; + default: + LOG.error("Unknown Mastership state : {}", newRole); + + } + LOG.debug("Accepting mastership role change for device {}", deviceId); + } + + private void pushPortMetrics(Dpid dpid, List<OFPortStatsEntry> portStatsEntries) { + DeviceId deviceId = DeviceId.deviceId(dpid.uri(dpid)); + Collection<PortStatistics> stats = buildPortStatistics(deviceId, portStatsEntries); + providerService.updatePortStatistics(deviceId, stats); + } + + private Collection<PortStatistics> buildPortStatistics(DeviceId deviceId, + List<OFPortStatsEntry> entries) { + HashSet<PortStatistics> stats = Sets.newHashSet(); + + for (OFPortStatsEntry entry : entries) { + try { + if (entry == null || entry.getPortNo() == null || entry.getPortNo().getPortNumber() < 0) { + continue; + } + DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder(); + DefaultPortStatistics stat = builder.setDeviceId(deviceId) + .setPort(entry.getPortNo().getPortNumber()) + .setPacketsReceived(entry.getRxPackets().getValue()) + .setPacketsSent(entry.getTxPackets().getValue()) + .setBytesReceived(entry.getRxBytes().getValue()) + .setBytesSent(entry.getTxBytes().getValue()) + .setPacketsRxDropped(entry.getRxDropped().getValue()) + .setPacketsTxDropped(entry.getTxDropped().getValue()) + .setPacketsRxErrors(entry.getRxErrors().getValue()) + .setPacketsTxErrors(entry.getTxErrors().getValue()) + .setDurationSec(entry.getVersion() == OFVersion.OF_10 ? 0 : entry.getDurationSec()) + .setDurationNano(entry.getVersion() == OFVersion.OF_10 ? 0 : entry.getDurationNsec()) + .build(); + + stats.add(stat); + } catch (Exception e) { + LOG.warn("Unable to process port stats", e); + } + } + + return Collections.unmodifiableSet(stats); + + } + + private class InternalDeviceProvider implements OpenFlowSwitchListener, OpenFlowEventListener { + + private HashMap<Dpid, List<OFPortStatsEntry>> portStatsReplies = new HashMap<>(); + + @Override + public void switchAdded(Dpid dpid) { + if (providerService == null) { + return; + } + DeviceId did = deviceId(uri(dpid)); + OpenFlowSwitch sw = controller.getSwitch(dpid); + + ChassisId cId = new ChassisId(dpid.value()); + + SparseAnnotations annotations = DefaultAnnotations.builder() + .set("protocol", sw.factory().getVersion().toString()) + .set("channelId", sw.channelId()) + .build(); + + DeviceDescription description = + new DefaultDeviceDescription(did.uri(), sw.deviceType(), + sw.manufacturerDescription(), + sw.hardwareDescription(), + sw.softwareDescription(), + sw.serialNumber(), + cId, annotations); + providerService.deviceConnected(did, description); + providerService.updatePorts(did, buildPortDescriptions(sw)); + + PortStatsCollector psc = + new PortStatsCollector(controller.getSwitch(dpid), portStatsPollFrequency); + psc.start(); + collectors.put(dpid, psc); + } + + @Override + public void switchRemoved(Dpid dpid) { + if (providerService == null) { + return; + } + providerService.deviceDisconnected(deviceId(uri(dpid))); + + PortStatsCollector collector = collectors.remove(dpid); + if (collector != null) { + collector.stop(); + } + } + + @Override + public void switchChanged(Dpid dpid) { + if (providerService == null) { + return; + } + DeviceId did = deviceId(uri(dpid)); + OpenFlowSwitch sw = controller.getSwitch(dpid); + providerService.updatePorts(did, buildPortDescriptions(sw)); + } + + @Override + public void portChanged(Dpid dpid, OFPortStatus status) { + PortDescription portDescription = buildPortDescription(status); + providerService.portStatusChanged(deviceId(uri(dpid)), portDescription); + } + + @Override + public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) { + MastershipRole request = roleOf(requested); + MastershipRole reply = roleOf(response); + + providerService.receivedRoleReply(deviceId(uri(dpid)), request, reply); + } + + /** + * Translates a RoleState to the corresponding MastershipRole. + * + * @param response role state + * @return a MastershipRole + */ + private MastershipRole roleOf(RoleState response) { + switch (response) { + case MASTER: + return MastershipRole.MASTER; + case EQUAL: + return MastershipRole.STANDBY; + case SLAVE: + return MastershipRole.NONE; + default: + LOG.warn("unknown role {}", response); + return null; + } + } + + /** + * Builds a list of port descriptions for a given list of ports. + * + * @return list of portdescriptions + */ + private List<PortDescription> buildPortDescriptions(OpenFlowSwitch sw) { + final List<PortDescription> portDescs = new ArrayList<>(sw.getPorts().size()); + sw.getPorts().forEach(port -> portDescs.add(buildPortDescription(port))); + + OpenFlowOpticalSwitch opsw; + switch (sw.deviceType()) { + case ROADM: + opsw = (OpenFlowOpticalSwitch) sw; + opsw.getPortTypes().forEach(type -> { + opsw.getPortsOf(type).forEach( + op -> { + portDescs.add(buildPortDescription(type, (OFPortOptical) op)); + } + ); + }); + break; + case FIBER_SWITCH: + opsw = (OpenFlowOpticalSwitch) sw; + opsw.getPortTypes().forEach(type -> { + opsw.getPortsOf(type).forEach( + op -> { + portDescs.add(buildPortDescription((OFCalientPortDescStatsEntry) op)); + } + ); + }); + break; + default: + break; + } + + return portDescs; + } + + /** + * Creates an annotation for the port name if one is available. + * + * @param port description of the port + * @return annotation containing the port name if one is found, + * null otherwise + */ + private SparseAnnotations makePortNameAnnotation(String port) { + SparseAnnotations annotations = null; + String portName = Strings.emptyToNull(port); + if (portName != null) { + annotations = DefaultAnnotations.builder() + .set(AnnotationKeys.PORT_NAME, portName).build(); + } + return annotations; + } + + /** + * Build a portDescription from a given Ethernet port description. + * + * @param port the port to build from. + * @return portDescription for the port. + */ + private PortDescription buildPortDescription(OFPortDesc port) { + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + boolean enabled = + !port.getState().contains(OFPortState.LINK_DOWN) && + !port.getConfig().contains(OFPortConfig.PORT_DOWN); + Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER; + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + return new DefaultPortDescription(portNo, enabled, type, + portSpeed(port), annotations); + } + + /** + * Build a portDescription from a given a port description describing some + * Optical port. + * + * @param port description property type. + * @param port the port to build from. + * @return portDescription for the port. + */ + private PortDescription buildPortDescription(PortDescPropertyType ptype, OFPortOptical port) { + checkArgument(port.getDesc().size() >= 1); + + // Minimally functional fixture. This needs to be fixed as we add better support. + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + + boolean enabled = !port.getState().contains(OFPortState.LINK_DOWN) + && !port.getConfig().contains(OFPortConfig.PORT_DOWN); + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + + if (port.getVersion() == OFVersion.OF_13 + && ptype == PortDescPropertyType.OPTICAL_TRANSPORT) { + // At this point, not much is carried in the optical port message. + LOG.debug("Optical transport port message {}", port.toString()); + } else { + // removable once 1.4+ support complete. + LOG.debug("Unsupported optical port properties"); + } + + OFPortDescPropOpticalTransport desc = port.getDesc().get(0); + switch (desc.getPortSignalType()) { + // FIXME: use constants once loxi has full optical extensions + case 2: // OMS port + // Assume complete optical spectrum and 50 GHz grid + // LINC-OE is only supported optical OF device for now + return new OmsPortDescription(portNo, enabled, + Spectrum.U_BAND_MIN, Spectrum.O_BAND_MAX, Frequency.ofGHz(50), annotations); + case 5: // OCH port + OchSignal signal = new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, 0, 4); + return new OchPortDescription(portNo, enabled, OduSignalType.ODU4, + true, signal, annotations); + default: + break; + } + + return new DefaultPortDescription(portNo, enabled, FIBER, 0, annotations); + } + + /** + * Build a portDescription from a given port description describing a fiber switch optical port. + * + * @param port description property type. + * @param port the port to build from. + * @return portDescription for the port. + */ + private PortDescription buildPortDescription(OFCalientPortDescStatsEntry port) { + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + + // FIXME when Calient OF agent reports port status + boolean enabled = true; + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + + // S160 data sheet + // Wavelength range: 1260 - 1630 nm, grid is irrelevant for this type of switch + return new OmsPortDescription(portNo, enabled, + Spectrum.U_BAND_MIN, Spectrum.O_BAND_MAX, Frequency.ofGHz(100), annotations); + } + + private PortDescription buildPortDescription(OFPortStatus status) { + OFPortDesc port = status.getDesc(); + if (status.getReason() != OFPortReason.DELETE) { + return buildPortDescription(port); + } else { + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER; + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + return new DefaultPortDescription(portNo, false, type, + portSpeed(port), annotations); + } + } + + private long portSpeed(OFPortDesc port) { + if (port.getVersion() == OFVersion.OF_13) { + return port.getCurrSpeed() / MBPS; + } + + PortSpeed portSpeed = PortSpeed.SPEED_NONE; + for (OFPortFeatures feat : port.getCurr()) { + portSpeed = PortSpeed.max(portSpeed, feat.getPortSpeed()); + } + return portSpeed.getSpeedBps() / MBPS; + } + + @Override + public void handleMessage(Dpid dpid, OFMessage msg) { + switch (msg.getType()) { + case STATS_REPLY: + if (((OFStatsReply) msg).getStatsType() == OFStatsType.PORT) { + OFPortStatsReply portStatsReply = (OFPortStatsReply) msg; + List<OFPortStatsEntry> portStatsReplyList = portStatsReplies.get(dpid); + if (portStatsReplyList == null) { + portStatsReplyList = Lists.newArrayList(); + } + portStatsReplyList.addAll(portStatsReply.getEntries()); + portStatsReplies.put(dpid, portStatsReplyList); + if (!portStatsReply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { + pushPortMetrics(dpid, portStatsReplies.get(dpid)); + portStatsReplies.get(dpid).clear(); + } + } + break; + default: + break; + } + } + } + +} diff --git a/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/PortStatsCollector.java b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/PortStatsCollector.java new file mode 100644 index 00000000..8383fa3f --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/PortStatsCollector.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.of.device.impl; + +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFPortStatsRequest; +import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static org.slf4j.LoggerFactory.getLogger; + +/* + * Sends Group Stats Request and collect the group statistics with a time interval. + */ +public class PortStatsCollector implements TimerTask { + + // TODO: Refactoring is required using ScheduledExecutorService + + private final HashedWheelTimer timer = Timer.getTimer(); + private final OpenFlowSwitch sw; + private final Logger log = getLogger(getClass()); + private int refreshInterval; + private final AtomicLong xidAtomic = new AtomicLong(1); + + private Timeout timeout; + private volatile boolean stopped; + + /** + * Creates a GroupStatsCollector object. + * + * @param sw Open Flow switch + * @param interval time interval for collecting group statistic + */ + public PortStatsCollector(OpenFlowSwitch sw, int interval) { + this.sw = sw; + this.refreshInterval = interval; + } + + @Override + public void run(Timeout to) throws Exception { + if (stopped || timeout.isCancelled()) { + return; + } + log.trace("Collecting stats for {}", sw.getStringId()); + + sendPortStatistic(); + + if (!stopped && !timeout.isCancelled()) { + log.trace("Scheduling stats collection in {} seconds for {}", + this.refreshInterval, this.sw.getStringId()); + timeout.getTimer().newTimeout(this, refreshInterval, TimeUnit.SECONDS); + } + } + + synchronized void adjustPollInterval(int pollInterval) { + this.refreshInterval = pollInterval; + // task.cancel(); + // task = new InternalTimerTask(); + // timer.scheduleAtFixedRate(task, pollInterval * SECONDS, pollInterval * 1000); + } + + private void sendPortStatistic() { + if (sw.getRole() != RoleState.MASTER) { + return; + } + Long statsXid = xidAtomic.getAndIncrement(); + OFPortStatsRequest statsRequest = sw.factory().buildPortStatsRequest() + .setPortNo(OFPort.ANY) + .setXid(statsXid) + .build(); + sw.sendMsg(statsRequest); + } + + /** + * Starts the collector. + */ + public synchronized void start() { + log.info("Starting Port Stats collection thread for {}", sw.getStringId()); + stopped = false; + timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS); + } + + /** + * Stops the collector. + */ + public synchronized void stop() { + log.info("Stopping Port Stats collection thread for {}", sw.getStringId()); + stopped = true; + timeout.cancel(); + } +} diff --git a/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/package-info.java b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/package-info.java new file mode 100644 index 00000000..9376b47d --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses OpenFlow controller as a means of infrastructure device discovery. + */ +package org.onosproject.provider.of.device.impl; diff --git a/framework/src/onos/providers/openflow/device/src/test/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProviderTest.java b/framework/src/onos/providers/openflow/device/src/test/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProviderTest.java new file mode 100644 index 00000000..7b4d7922 --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/test/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProviderTest.java @@ -0,0 +1,402 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.device.impl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.PacketListener; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.ver10.OFFactoryVer10; +import org.projectfloodlight.openflow.types.OFPort; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.MastershipRole.*; + +public class OpenFlowDeviceProviderTest { + + private static final ProviderId PID = new ProviderId("of", "test"); + private static final DeviceId DID1 = DeviceId.deviceId("of:0000000000000001"); + private static final Dpid DPID1 = Dpid.dpid(DID1.uri()); + + private static final OFPortDesc PD1 = portDesc(1); + private static final OFPortDesc PD2 = portDesc(2); + private static final OFPortDesc PD3 = portDesc(3); + + private static final List<OFPortDesc> PLIST = Lists.newArrayList(PD1, PD2); + + private static final Device DEV1 = + new DefaultDevice(PID, DID1, SWITCH, "", "", "", "", null); + + private static final TestOpenFlowSwitch SW1 = new TestOpenFlowSwitch(); + + private final OpenFlowDeviceProvider provider = new OpenFlowDeviceProvider(); + private final TestDeviceRegistry registry = new TestDeviceRegistry(); + private final TestController controller = new TestController(); + + @Before + public void startUp() { + provider.providerRegistry = registry; + provider.controller = controller; + provider.cfgService = new ComponentConfigAdapter(); + controller.switchMap.put(DPID1, SW1); + provider.activate(null); + assertNotNull("provider should be registered", registry.provider); + assertNotNull("listener should be registered", controller.listener); + assertEquals("devices not added", 1, registry.connected.size()); + assertEquals("ports not added", 2, registry.ports.get(DID1).size()); + } + + @After + public void tearDown() { + provider.deactivate(null); + assertNull("listener should be removed", controller.listener); + provider.controller = null; + provider.providerRegistry = null; + } + + @Test + public void roleChanged() { + provider.roleChanged(DID1, MASTER); + assertEquals("Should be MASTER", RoleState.MASTER, controller.roleMap.get(DPID1)); + provider.roleChanged(DID1, STANDBY); + assertEquals("Should be EQUAL", RoleState.EQUAL, controller.roleMap.get(DPID1)); + provider.roleChanged(DID1, NONE); + assertEquals("Should be SLAVE", RoleState.SLAVE, controller.roleMap.get(DPID1)); + } + + @Test + public void triggerProbe() { + + } + + @Test + public void switchRemoved() { + controller.listener.switchRemoved(DPID1); + assertTrue("device not removed", registry.connected.isEmpty()); + } + + @Test + public void portChanged() { + OFPortStatus stat = SW1.factory().buildPortStatus() + .setReason(OFPortReason.ADD) + .setDesc(PD3) + .build(); + controller.listener.portChanged(DPID1, stat); + assertNotNull("never went throught the provider service", registry.descr); + assertEquals("port status unhandled", 3, registry.ports.get(DID1).size()); + } + + @Test + public void receivedRoleReply() { + // check translation capabilities + controller.listener.receivedRoleReply(DPID1, RoleState.MASTER, RoleState.MASTER); + assertEquals("wrong role reported", DPID1, registry.roles.get(MASTER)); + controller.listener.receivedRoleReply(DPID1, RoleState.EQUAL, RoleState.MASTER); + assertEquals("wrong role reported", DPID1, registry.roles.get(STANDBY)); + controller.listener.receivedRoleReply(DPID1, RoleState.SLAVE, RoleState.MASTER); + assertEquals("wrong role reported", DPID1, registry.roles.get(NONE)); + } + + private static OFPortDesc portDesc(int port) { + OFPortDesc.Builder builder = OFFactoryVer10.INSTANCE.buildPortDesc(); + builder.setPortNo(OFPort.of(port)); + + return builder.build(); + } + + private class TestDeviceRegistry implements DeviceProviderRegistry { + DeviceProvider provider; + + Set<DeviceId> connected = new HashSet<>(); + Multimap<DeviceId, PortDescription> ports = HashMultimap.create(); + PortDescription descr = null; + Map<MastershipRole, Dpid> roles = new HashMap<>(); + + @Override + public DeviceProviderService register(DeviceProvider provider) { + this.provider = provider; + return new TestProviderService(); + } + + @Override + public void unregister(DeviceProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + private class TestProviderService implements DeviceProviderService { + + @Override + public DeviceProvider provider() { + return null; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + connected.add(deviceId); + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + connected.remove(deviceId); + ports.removeAll(deviceId); + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + for (PortDescription p : portDescriptions) { + ports.put(deviceId, p); + } + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + ports.put(deviceId, portDescription); + descr = portDescription; + } + + @Override + public void receivedRoleReply(DeviceId deviceId, + MastershipRole requested, MastershipRole response) { + roles.put(requested, Dpid.dpid(deviceId.uri())); + } + + @Override + public void updatePortStatistics(DeviceId deviceId, Collection<PortStatistics> portStatistics) { + + } + + } + } + + private class TestController implements OpenFlowController { + OpenFlowSwitchListener listener = null; + Map<Dpid, RoleState> roleMap = new HashMap<Dpid, RoleState>(); + Map<Dpid, OpenFlowSwitch> switchMap = new HashMap<Dpid, OpenFlowSwitch>(); + + @Override + public Iterable<OpenFlowSwitch> getSwitches() { + return switchMap.values(); + } + + @Override + public Iterable<OpenFlowSwitch> getMasterSwitches() { + return null; + } + + @Override + public Iterable<OpenFlowSwitch> getEqualSwitches() { + return null; + } + + @Override + public OpenFlowSwitch getSwitch(Dpid dpid) { + return switchMap.get(dpid); + } + + @Override + public OpenFlowSwitch getMasterSwitch(Dpid dpid) { + return null; + } + + @Override + public OpenFlowSwitch getEqualSwitch(Dpid dpid) { + + return null; + } + + @Override + public void addListener(OpenFlowSwitchListener listener) { + this.listener = listener; + } + + @Override + public void removeListener(OpenFlowSwitchListener listener) { + this.listener = null; + } + + @Override + public void addPacketListener(int priority, PacketListener listener) { + } + + @Override + public void removePacketListener(PacketListener listener) { + } + + @Override + public void addEventListener(OpenFlowEventListener listener) { + } + + @Override + public void removeEventListener(OpenFlowEventListener listener) { + } + + @Override + public void write(Dpid dpid, OFMessage msg) { + } + + @Override + public void processPacket(Dpid dpid, OFMessage msg) { + } + + @Override + public void setRole(Dpid dpid, RoleState role) { + roleMap.put(dpid, role); + } + } + + private static class TestOpenFlowSwitch implements OpenFlowSwitch { + + RoleState state; + List<OFMessage> sent = new ArrayList<OFMessage>(); + OFFactory factory = OFFactoryVer10.INSTANCE; + + @Override + public void sendMsg(OFMessage msg) { + sent.add(msg); + } + + @Override + public void sendMsg(List<OFMessage> msgs) { + } + + @Override + public void handleMessage(OFMessage fromSwitch) { + } + + @Override + public void setRole(RoleState role) { + state = role; + } + + @Override + public RoleState getRole() { + return state; + } + + @Override + public List<OFPortDesc> getPorts() { + return PLIST; + } + + @Override + public OFFactory factory() { + return factory; + } + + @Override + public String getStringId() { + return null; + } + + @Override + public long getId() { + return DPID1.value(); + } + + @Override + public String manufacturerDescription() { + return null; + } + + @Override + public String datapathDescription() { + return null; + } + + @Override + public String hardwareDescription() { + return null; + } + + @Override + public String softwareDescription() { + return null; + } + + @Override + public String serialNumber() { + return null; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public void disconnectSwitch() { + } + + @Override + public Device.Type deviceType() { + return Device.Type.SWITCH; + } + + @Override + public void returnRoleReply(RoleState requested, RoleState response) { + } + + @Override + public String channelId() { + return "1.2.3.4:1"; + } + + } + +} |