diff options
Diffstat (limited to 'framework/src/onos/providers/lldp')
10 files changed, 1911 insertions, 0 deletions
diff --git a/framework/src/onos/providers/lldp/pom.xml b/framework/src/onos/providers/lldp/pom.xml new file mode 100644 index 00000000..bc13793e --- /dev/null +++ b/framework/src/onos/providers/lldp/pom.xml @@ -0,0 +1,55 @@ +<?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-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-lldp-provider</artifactId> + <packaging>bundle</packaging> + + <description>ONOS LLDP Link Discovery</description> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> +</project> diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LLDPLinkProvider.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LLDPLinkProvider.java new file mode 100644 index 00000000..e9e2bfad --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LLDPLinkProvider.java @@ -0,0 +1,452 @@ +/* + * 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.lldp.impl; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +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.Ethernet; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Dictionary; +import java.util.EnumSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; + +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.onlab.util.Tools.get; +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to detect network + * infrastructure links. + */ +@Component(immediate = true) +public class LLDPLinkProvider extends AbstractProvider implements LinkProvider { + + private static final String PROVIDER_NAME = "org.onosproject.provider.lldp"; + + private static final String PROP_USE_BDDP = "useBDDP"; + private static final String PROP_DISABLE_LD = "disableLinkDiscovery"; + private static final String PROP_LLDP_SUPPRESSION = "lldpSuppression"; + + private static final String DEFAULT_LLDP_SUPPRESSION_CONFIG = "../config/lldp_suppression.json"; + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService masterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private LinkProviderService providerService; + + private ScheduledExecutorService executor; + + @Property(name = PROP_USE_BDDP, boolValue = true, + label = "Use BDDP for link discovery") + private boolean useBDDP = true; + + @Property(name = PROP_DISABLE_LD, boolValue = false, + label = "Permanently disable link discovery") + private boolean disableLinkDiscovery = false; + + private static final long INIT_DELAY = 5; + private static final long DELAY = 5; + + @Property(name = PROP_LLDP_SUPPRESSION, value = DEFAULT_LLDP_SUPPRESSION_CONFIG, + label = "Path to LLDP suppression configuration file") + private String lldpSuppression = DEFAULT_LLDP_SUPPRESSION_CONFIG; + + + private final InternalLinkProvider listener = new InternalLinkProvider(); + + private final InternalRoleListener roleListener = new InternalRoleListener(); + + protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>(); + + private SuppressionRules rules; + private ApplicationId appId; + + /** + * Creates an OpenFlow link provider. + */ + public LLDPLinkProvider() { + super(new ProviderId("lldp", PROVIDER_NAME)); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + appId = coreService.registerApplication(PROVIDER_NAME); + + // to load configuration at startup + modified(context); + if (disableLinkDiscovery) { + log.info("LinkDiscovery has been permanently disabled by configuration"); + return; + } + + providerService = providerRegistry.register(this); + deviceService.addListener(listener); + packetService.addProcessor(listener, PacketProcessor.advisor(0)); + masterService.addListener(roleListener); + + LinkDiscovery ld; + for (Device device : deviceService.getAvailableDevices()) { + if (rules.isSuppressed(device)) { + log.debug("LinkDiscovery from {} disabled by configuration", device.id()); + continue; + } + ld = new LinkDiscovery(device, packetService, masterService, + providerService, useBDDP); + discoverers.put(device.id(), ld); + addPorts(ld, device.id()); + } + + executor = newSingleThreadScheduledExecutor(groupedThreads("onos/device", "sync-%d")); + executor.scheduleAtFixedRate(new SyncDeviceInfoTask(), INIT_DELAY, DELAY, SECONDS); + + requestIntercepts(); + + log.info("Started"); + } + + private void addPorts(LinkDiscovery discoverer, DeviceId deviceId) { + for (Port p : deviceService.getPorts(deviceId)) { + if (rules.isSuppressed(p)) { + continue; + } + if (!p.number().isLogical()) { + discoverer.addPort(p); + } + } + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + if (disableLinkDiscovery) { + return; + } + + withdrawIntercepts(); + + providerRegistry.unregister(this); + deviceService.removeListener(listener); + packetService.removeProcessor(listener); + masterService.removeListener(roleListener); + + executor.shutdownNow(); + discoverers.values().forEach(LinkDiscovery::stop); + discoverers.clear(); + providerService = null; + + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + loadSuppressionRules(); + return; + } + @SuppressWarnings("rawtypes") + Dictionary properties = context.getProperties(); + + String s = get(properties, PROP_DISABLE_LD); + if (!Strings.isNullOrEmpty(s)) { + disableLinkDiscovery = Boolean.valueOf(s); + } + s = get(properties, PROP_USE_BDDP); + if (!Strings.isNullOrEmpty(s)) { + useBDDP = Boolean.valueOf(s); + } + s = get(properties, PROP_LLDP_SUPPRESSION); + if (!Strings.isNullOrEmpty(s)) { + lldpSuppression = s; + } + requestIntercepts(); + loadSuppressionRules(); + } + + private void loadSuppressionRules() { + SuppressionRulesStore store = new SuppressionRulesStore(lldpSuppression); + try { + log.info("Reading suppression rules from {}", lldpSuppression); + rules = store.read(); + } catch (IOException e) { + log.info("Failed to load {}, using built-in rules", lldpSuppression); + // default rule to suppress ROADM to maintain compatibility + rules = new SuppressionRules(ImmutableSet.of(), + EnumSet.of(Device.Type.ROADM), + ImmutableMap.of()); + } + + // should refresh discoverers when we need dynamic reconfiguration + } + + /** + * Request packet intercepts. + */ + private void requestIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_LLDP); + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + + selector.matchEthType(Ethernet.TYPE_BSN); + if (useBDDP) { + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + } else { + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + } + + /** + * Withdraw packet intercepts. + */ + private void withdrawIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_LLDP); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + selector.matchEthType(Ethernet.TYPE_BSN); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + + private LinkDiscovery createLinkDiscovery(Device device) { + return new LinkDiscovery(device, packetService, masterService, + providerService, useBDDP); + } + + private class InternalRoleListener implements MastershipListener { + + @Override + public void event(MastershipEvent event) { + if (MastershipEvent.Type.BACKUPS_CHANGED.equals(event.type())) { + // only need new master events + return; + } + + DeviceId deviceId = event.subject(); + Device device = deviceService.getDevice(deviceId); + if (device == null) { + log.debug("Device {} doesn't exist, or isn't there yet", deviceId); + return; + } + if (rules.isSuppressed(device)) { + return; + } + synchronized (discoverers) { + if (!discoverers.containsKey(deviceId)) { + // ideally, should never reach here + log.debug("Device mastership changed ({}) {}", event.type(), deviceId); + discoverers.put(deviceId, createLinkDiscovery(device)); + } + } + } + + } + + private class InternalLinkProvider implements PacketProcessor, DeviceListener { + + @Override + public void event(DeviceEvent event) { + LinkDiscovery ld = null; + Device device = event.subject(); + Port port = event.port(); + if (device == null) { + log.error("Device is null."); + return; + } + log.trace("{} {} {}", event.type(), event.subject(), event); + final DeviceId deviceId = device.id(); + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_UPDATED: + synchronized (discoverers) { + ld = discoverers.get(deviceId); + if (ld == null) { + if (rules.isSuppressed(device)) { + log.debug("LinkDiscovery from {} disabled by configuration", device.id()); + return; + } + log.debug("Device added ({}) {}", event.type(), deviceId); + discoverers.put(deviceId, createLinkDiscovery(device)); + } else { + if (ld.isStopped()) { + log.debug("Device restarted ({}) {}", event.type(), deviceId); + ld.start(); + } + } + } + break; + case PORT_ADDED: + case PORT_UPDATED: + if (port.isEnabled()) { + ld = discoverers.get(deviceId); + if (ld == null) { + return; + } + if (rules.isSuppressed(port)) { + log.debug("LinkDiscovery from {}@{} disabled by configuration", + port.number(), device.id()); + return; + } + if (!port.number().isLogical()) { + log.debug("Port added {}", port); + ld.addPort(port); + } + } else { + log.debug("Port down {}", port); + ConnectPoint point = new ConnectPoint(deviceId, port.number()); + providerService.linksVanished(point); + } + break; + case PORT_REMOVED: + log.debug("Port removed {}", port); + ConnectPoint point = new ConnectPoint(deviceId, port.number()); + providerService.linksVanished(point); + + break; + case DEVICE_REMOVED: + case DEVICE_SUSPENDED: + log.debug("Device removed {}", deviceId); + ld = discoverers.get(deviceId); + if (ld == null) { + return; + } + ld.stop(); + providerService.linksVanished(deviceId); + break; + case DEVICE_AVAILABILITY_CHANGED: + ld = discoverers.get(deviceId); + if (ld == null) { + return; + } + if (deviceService.isAvailable(deviceId)) { + log.debug("Device up {}", deviceId); + ld.start(); + } else { + providerService.linksVanished(deviceId); + log.debug("Device down {}", deviceId); + ld.stop(); + } + break; + case PORT_STATS_UPDATED: + break; + default: + log.debug("Unknown event {}", event); + } + } + + @Override + public void process(PacketContext context) { + if (context == null) { + return; + } + LinkDiscovery ld = discoverers.get(context.inPacket().receivedFrom().deviceId()); + if (ld == null) { + return; + } + + if (ld.handleLLDP(context)) { + context.block(); + } + } + } + + private final class SyncDeviceInfoTask implements Runnable { + + @Override + public void run() { + if (Thread.currentThread().isInterrupted()) { + log.info("Interrupted, quitting"); + return; + } + // check what deviceService sees, to see if we are missing anything + try { + for (Device dev : deviceService.getDevices()) { + if (rules.isSuppressed(dev)) { + continue; + } + DeviceId did = dev.id(); + synchronized (discoverers) { + LinkDiscovery discoverer = discoverers.get(did); + if (discoverer == null) { + discoverer = createLinkDiscovery(dev); + discoverers.put(did, discoverer); + } + + addPorts(discoverer, did); + } + } + } catch (Exception e) { + // catch all Exception to avoid Scheduled task being suppressed. + log.error("Exception thrown during synchronization process", e); + } + } + } + +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java new file mode 100644 index 00000000..a81eeb1d --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java @@ -0,0 +1,369 @@ +/* + * 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.lldp.impl; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ONOSLLDP; +import org.onlab.util.Timer; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link.Type; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketService; +import org.slf4j.Logger; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.flow.DefaultTrafficTreatment.builder; +import static org.slf4j.LoggerFactory.getLogger; + +// TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen for flow_removed messages +// FIXME: add ability to track links using port pairs or the link inventory + +/** + * Run discovery process from a physical switch. Ports are initially labeled as + * slow ports. When an LLDP is successfully received, label the remote port as + * fast. Every probeRate milliseconds, loop over all fast ports and send an + * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology + * discovery implementation. + */ +public class LinkDiscovery implements TimerTask { + + private final Logger log = getLogger(getClass()); + + private static final short MAX_PROBE_COUNT = 3; // probes to send before link is removed + private static final short DEFAULT_PROBE_RATE = 3000; // millis + private static final String SRC_MAC = "DE:AD:BE:EF:BA:11"; + private static final String SERVICE_NULL = "Service cannot be null"; + + private final Device device; + + // send 1 probe every probeRate milliseconds + private final long probeRate = DEFAULT_PROBE_RATE; + + private final Set<Long> slowPorts = Sets.newConcurrentHashSet(); + // ports, known to have incoming links + private final Set<Long> fastPorts = Sets.newConcurrentHashSet(); + + // number of unacknowledged probes per port + private final Map<Long, AtomicInteger> portProbeCount = Maps.newHashMap(); + + private final ONOSLLDP lldpPacket; + private final Ethernet ethPacket; + private Ethernet bddpEth; + private final boolean useBDDP; + + private Timeout timeout; + private volatile boolean isStopped; + + private final LinkProviderService linkProvider; + private final PacketService pktService; + private final MastershipService mastershipService; + + /** + * Instantiates discovery manager for the given physical switch. Creates a + * generic LLDP packet that will be customized for the port it is sent out on. + * Starts the the timer for the discovery process. + * + * @param device the physical switch + * @param pktService packet service + * @param masterService mastership service + * @param providerService link provider service + * @param useBDDP flag to also use BDDP for discovery + */ + public LinkDiscovery(Device device, PacketService pktService, + MastershipService masterService, + LinkProviderService providerService, Boolean... useBDDP) { + this.device = device; + this.linkProvider = checkNotNull(providerService, SERVICE_NULL); + this.pktService = checkNotNull(pktService, SERVICE_NULL); + this.mastershipService = checkNotNull(masterService, SERVICE_NULL); + + lldpPacket = new ONOSLLDP(); + lldpPacket.setChassisId(device.chassisId()); + lldpPacket.setDevice(device.id().toString()); + + ethPacket = new Ethernet(); + ethPacket.setEtherType(Ethernet.TYPE_LLDP); + ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA); + ethPacket.setPayload(this.lldpPacket); + ethPacket.setPad(true); + + this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false; + if (this.useBDDP) { + bddpEth = new Ethernet(); + bddpEth.setPayload(lldpPacket); + bddpEth.setEtherType(Ethernet.TYPE_BSN); + bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST); + bddpEth.setPad(true); + log.info("Using BDDP to discover network"); + } + + isStopped = true; + start(); + log.debug("Started discovery manager for switch {}", device.id()); + + } + + /** + * Add physical port port to discovery process. + * Send out initial LLDP and label it as slow port. + * + * @param port the port + */ + public void addPort(Port port) { + boolean newPort = false; + synchronized (this) { + if (!containsPort(port.number().toLong())) { + newPort = true; + slowPorts.add(port.number().toLong()); + } + } + + boolean isMaster = mastershipService.isLocalMaster(device.id()); + if (newPort && isMaster) { + log.debug("Sending init probe to port {}@{}", port.number().toLong(), device.id()); + sendProbes(port.number().toLong()); + } + } + + /** + * Removes physical port from discovery process. + * + * @param port the port + */ + public void removePort(Port port) { + // Ignore ports that are not on this switch + long portnum = port.number().toLong(); + synchronized (this) { + if (slowPorts.contains(portnum)) { + slowPorts.remove(portnum); + + } else if (fastPorts.contains(portnum)) { + fastPorts.remove(portnum); + portProbeCount.remove(portnum); + // no iterator to update + } else { + log.warn("Tried to dynamically remove non-existing port {}", portnum); + } + } + } + + /** + * Method called by remote port to acknowledge receipt of LLDP sent by + * this port. If slow port, updates label to fast. If fast port, decrements + * number of unacknowledged probes. + * + * @param portNumber the port + */ + public void ackProbe(Long portNumber) { + synchronized (this) { + if (slowPorts.contains(portNumber)) { + log.debug("Setting slow port to fast: {}:{}", device.id(), portNumber); + slowPorts.remove(portNumber); + fastPorts.add(portNumber); + portProbeCount.put(portNumber, new AtomicInteger(0)); + } else if (fastPorts.contains(portNumber)) { + portProbeCount.get(portNumber).set(0); + } else { + log.debug("Got ackProbe for non-existing port: {}", portNumber); + } + } + } + + + /** + * Handles an incoming LLDP packet. Creates link in topology and sends ACK + * to port where LLDP originated. + * + * @param context packet context + * @return true if handled + */ + public boolean handleLLDP(PacketContext context) { + Ethernet eth = context.inPacket().parsed(); + if (eth == null) { + return false; + } + + ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth); + if (onoslldp != null) { + PortNumber srcPort = portNumber(onoslldp.getPort()); + PortNumber dstPort = context.inPacket().receivedFrom().port(); + DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString()); + DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId(); + ackProbe(dstPort.toLong()); + + ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort); + ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort); + + LinkDescription ld = eth.getEtherType() == Ethernet.TYPE_LLDP ? + new DefaultLinkDescription(src, dst, Type.DIRECT) : + new DefaultLinkDescription(src, dst, Type.INDIRECT); + + try { + linkProvider.linkDetected(ld); + } catch (IllegalStateException e) { + return true; + } + return true; + } + return false; + } + + + /** + * Execute this method every t milliseconds. Loops over all ports + * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow + * port. + * + * @param t timeout + */ + @Override + public void run(Timeout t) { + if (isStopped()) { + return; + } + if (!mastershipService.isLocalMaster(device.id())) { + if (!isStopped()) { + // reschedule timer + timeout = Timer.getTimer().newTimeout(this, probeRate, MILLISECONDS); + } + return; + } + + log.trace("Sending probes from {}", device.id()); + synchronized (this) { + Iterator<Long> fastIterator = fastPorts.iterator(); + while (fastIterator.hasNext()) { + long portNumber = fastIterator.next(); + int probeCount = portProbeCount.get(portNumber).getAndIncrement(); + + if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) { + log.trace("Sending fast probe to port {}", portNumber); + sendProbes(portNumber); + + } else { + // Link down, demote to slowPorts; update fast and slow ports + fastIterator.remove(); + slowPorts.add(portNumber); + portProbeCount.remove(portNumber); + + ConnectPoint cp = new ConnectPoint(device.id(), portNumber(portNumber)); + log.debug("Link down -> {}", cp); + linkProvider.linksVanished(cp); + } + } + + // send a probe for the next slow port + for (long portNumber : slowPorts) { + log.trace("Sending slow probe to port {}", portNumber); + sendProbes(portNumber); + } + } + + if (!isStopped()) { + // reschedule timer + timeout = Timer.getTimer().newTimeout(this, probeRate, MILLISECONDS); + } + } + + public synchronized void stop() { + isStopped = true; + timeout.cancel(); + } + + public synchronized void start() { + if (isStopped) { + isStopped = false; + timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS); + } else { + log.warn("LinkDiscovery started multiple times?"); + } + } + + /** + * Creates packet_out LLDP for specified output port. + * + * @param port the port + * @return Packet_out message with LLDP data + */ + private OutboundPacket createOutBoundLLDP(Long port) { + if (port == null) { + return null; + } + lldpPacket.setPortId(port.intValue()); + ethPacket.setSourceMACAddress(SRC_MAC); + return new DefaultOutboundPacket(device.id(), + builder().setOutput(portNumber(port)).build(), + ByteBuffer.wrap(ethPacket.serialize())); + } + + /** + * Creates packet_out BDDP for specified output port. + * + * @param port the port + * @return Packet_out message with LLDP data + */ + private OutboundPacket createOutBoundBDDP(Long port) { + if (port == null) { + return null; + } + lldpPacket.setPortId(port.intValue()); + bddpEth.setSourceMACAddress(SRC_MAC); + return new DefaultOutboundPacket(device.id(), + builder().setOutput(portNumber(port)).build(), + ByteBuffer.wrap(bddpEth.serialize())); + } + + private void sendProbes(Long portNumber) { + log.trace("Sending probes out to {}@{}", portNumber, device.id()); + OutboundPacket pkt = createOutBoundLLDP(portNumber); + pktService.emit(pkt); + if (useBDDP) { + OutboundPacket bpkt = createOutBoundBDDP(portNumber); + pktService.emit(bpkt); + } + } + + public boolean containsPort(Long portNumber) { + return slowPorts.contains(portNumber) || fastPorts.contains(portNumber); + } + + public synchronized boolean isStopped() { + return isStopped || timeout.isCancelled(); + } + +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java new file mode 100644 index 00000000..27c75ebd --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java @@ -0,0 +1,106 @@ +/* + * 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.lldp.impl; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.onosproject.net.Annotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Element; +import org.onosproject.net.Port; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class SuppressionRules { + + public static final String ANY_VALUE = "(any)"; + + private final Set<DeviceId> suppressedDevice; + private final Set<Device.Type> suppressedDeviceType; + private final Map<String, String> suppressedAnnotation; + + public SuppressionRules(Set<DeviceId> suppressedDevice, + Set<Device.Type> suppressedType, + Map<String, String> suppressedAnnotation) { + + this.suppressedDevice = ImmutableSet.copyOf(suppressedDevice); + this.suppressedDeviceType = ImmutableSet.copyOf(suppressedType); + this.suppressedAnnotation = ImmutableMap.copyOf(suppressedAnnotation); + } + + public boolean isSuppressed(Device device) { + if (suppressedDevice.contains(device.id())) { + return true; + } + if (suppressedDeviceType.contains(device.type())) { + return true; + } + final Annotations annotations = device.annotations(); + if (containsSuppressionAnnotation(annotations)) { + return true; + } + return false; + } + + public boolean isSuppressed(Port port) { + Element parent = port.element(); + if (parent instanceof Device) { + if (isSuppressed((Device) parent)) { + return true; + } + } + + final Annotations annotations = port.annotations(); + if (containsSuppressionAnnotation(annotations)) { + return true; + } + return false; + } + + private boolean containsSuppressionAnnotation(final Annotations annotations) { + for (Entry<String, String> entry : suppressedAnnotation.entrySet()) { + final String suppValue = entry.getValue(); + final String suppKey = entry.getKey(); + if (suppValue == ANY_VALUE) { + if (annotations.keys().contains(suppKey)) { + return true; + } + } else { + if (suppValue.equals(annotations.value(suppKey))) { + return true; + } + } + } + return false; + } + + Set<DeviceId> getSuppressedDevice() { + return suppressedDevice; + } + + Set<Device.Type> getSuppressedDeviceType() { + return suppressedDeviceType; + } + + Map<String, String> getSuppressedAnnotation() { + return suppressedAnnotation; + } +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRulesStore.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRulesStore.java new file mode 100644 index 00000000..360bebd2 --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRulesStore.java @@ -0,0 +1,174 @@ +/* + * 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.lldp.impl; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.slf4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/* + * JSON file example + * + +{ + "deviceId" : [ "of:2222000000000000" ], + "deviceType" : [ "ROADM" ], + "annotation" : { "no-lldp" : null, "sendLLDP" : "false" } +} + */ + +/** + * Allows for reading and writing LLDP suppression definition as a JSON file. + */ +public class SuppressionRulesStore { + + private static final String DEVICE_ID = "deviceId"; + private static final String DEVICE_TYPE = "deviceType"; + private static final String ANNOTATION = "annotation"; + + private final Logger log = getLogger(getClass()); + + private final File file; + + /** + * Creates a reader/writer of the LLDP suppression definition file. + * + * @param filePath location of the definition file + */ + public SuppressionRulesStore(String filePath) { + file = new File(filePath); + } + + /** + * Creates a reader/writer of the LLDP suppression definition file. + * + * @param file definition file + */ + public SuppressionRulesStore(File file) { + this.file = checkNotNull(file); + } + + /** + * Returns SuppressionRules. + * + * @return SuppressionRules + * @throws IOException if error occurred while reading the data + */ + public SuppressionRules read() throws IOException { + final Set<DeviceId> suppressedDevice = new HashSet<>(); + final EnumSet<Device.Type> suppressedDeviceType = EnumSet.noneOf(Device.Type.class); + final Map<String, String> suppressedAnnotation = new HashMap<>(); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = (ObjectNode) mapper.readTree(file); + + for (JsonNode deviceId : root.get(DEVICE_ID)) { + if (deviceId.isTextual()) { + suppressedDevice.add(DeviceId.deviceId(deviceId.asText())); + } else { + log.warn("Encountered unexpected JSONNode {} for deviceId", deviceId); + } + } + + for (JsonNode deviceType : root.get(DEVICE_TYPE)) { + if (deviceType.isTextual()) { + suppressedDeviceType.add(Device.Type.valueOf(deviceType.asText())); + } else { + log.warn("Encountered unexpected JSONNode {} for deviceType", deviceType); + } + } + + JsonNode annotation = root.get(ANNOTATION); + if (annotation.isObject()) { + ObjectNode obj = (ObjectNode) annotation; + Iterator<Entry<String, JsonNode>> it = obj.fields(); + while (it.hasNext()) { + Entry<String, JsonNode> entry = it.next(); + final String key = entry.getKey(); + final JsonNode value = entry.getValue(); + + if (value.isValueNode()) { + if (value.isNull()) { + suppressedAnnotation.put(key, SuppressionRules.ANY_VALUE); + } else { + suppressedAnnotation.put(key, value.asText()); + } + } else { + log.warn("Encountered unexpected JSON field {} for annotation", entry); + } + } + } else { + log.warn("Encountered unexpected JSONNode {} for annotation", annotation); + } + + return new SuppressionRules(suppressedDevice, + suppressedDeviceType, + suppressedAnnotation); + } + + /** + * Writes the given SuppressionRules. + * + * @param rules SuppressionRules + * @throws IOException if error occurred while writing the data + */ + public void write(SuppressionRules rules) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = mapper.createObjectNode(); + ArrayNode deviceIds = mapper.createArrayNode(); + ArrayNode deviceTypes = mapper.createArrayNode(); + ObjectNode annotations = mapper.createObjectNode(); + root.set(DEVICE_ID, deviceIds); + root.set(DEVICE_TYPE, deviceTypes); + root.set(ANNOTATION, annotations); + + rules.getSuppressedDevice() + .forEach(deviceId -> deviceIds.add(deviceId.toString())); + + rules.getSuppressedDeviceType() + .forEach(type -> deviceTypes.add(type.toString())); + + rules.getSuppressedAnnotation().forEach((key, value) -> { + if (value == SuppressionRules.ANY_VALUE) { + annotations.putNull(key); + } else { + annotations.put(key, value); + } + }); + mapper.writeTree(new JsonFactory().createGenerator(file, JsonEncoding.UTF8), + root); + } +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/package-info.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/package-info.java new file mode 100644 index 00000000..768a6cd2 --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/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 the core as a means of infrastructure link inference. + */ +package org.onosproject.provider.lldp.impl; diff --git a/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java new file mode 100644 index 00000000..1d63a15d --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java @@ -0,0 +1,502 @@ +/* + * 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.lldp.impl; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ONOSLLDP; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.cluster.NodeId; +import org.onosproject.cluster.RoleInfo; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.packet.DefaultInboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketServiceAdapter; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class LLDPLinkProviderTest { + + private static final DeviceId DID1 = DeviceId.deviceId("of:0000000000000001"); + private static final DeviceId DID2 = DeviceId.deviceId("of:0000000000000002"); + + private static Port pd1; + private static Port pd2; + private static Port pd3; + private static Port pd4; + + private final LLDPLinkProvider provider = new LLDPLinkProvider(); + private final TestLinkRegistry linkService = new TestLinkRegistry(); + private final TestPacketService packetService = new TestPacketService(); + private final TestDeviceService deviceService = new TestDeviceService(); + private final TestMasterShipService masterService = new TestMasterShipService(); + + private CoreService coreService; + private TestLinkProviderService providerService; + + private PacketProcessor testProcessor; + private DeviceListener deviceListener; + + private ApplicationId appId = + new DefaultApplicationId(100, "org.onosproject.provider.lldp"); + + @Before + public void setUp() { + coreService = createMock(CoreService.class); + expect(coreService.registerApplication(appId.name())) + .andReturn(appId).anyTimes(); + replay(coreService); + + provider.cfgService = new ComponentConfigAdapter(); + provider.coreService = coreService; + + provider.deviceService = deviceService; + provider.packetService = packetService; + provider.providerRegistry = linkService; + provider.masterService = masterService; + + + provider.activate(null); + } + + @Test + public void basics() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + } + + @Test + public void switchAdd() { + DeviceEvent de = deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1); + deviceListener.event(de); + + assertFalse("Device not added", provider.discoverers.isEmpty()); + } + + @Test + public void switchRemove() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_REMOVED, DID1)); + + assertTrue("Discoverer is not gone", provider.discoverers.get(DID1).isStopped()); + assertTrue("Device is not gone.", vanishedDpid(DID1)); + } + + @Test + public void portUp() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 3, true))); + + assertTrue("Port not added to discoverer", + provider.discoverers.get(DID1).containsPort(3L)); + } + + @Test + public void portDown() { + + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 1, false))); + + + + assertFalse("Port added to discoverer", + provider.discoverers.get(DID1).containsPort(1L)); + assertTrue("Port is not gone.", vanishedPort(1L)); + } + + @Test + public void portUnknown() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID2, port(DID2, 1, false))); + + + assertNull("DeviceId exists", + provider.discoverers.get(DID2)); + } + + @Test + public void unknownPktCtx() { + + PacketContext pktCtx = new TestPacketContext(deviceService.getDevice(DID2)); + + testProcessor.process(pktCtx); + assertFalse("Context should still be free", pktCtx.isHandled()); + } + + @Test + public void knownPktCtx() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID2)); + PacketContext pktCtx = new TestPacketContext(deviceService.getDevice(DID2)); + + + testProcessor.process(pktCtx); + + assertTrue("Link not detected", detectedLink(DID1, DID2)); + + } + + + @After + public void tearDown() { + provider.deactivate(); + provider.coreService = null; + provider.providerRegistry = null; + provider.deviceService = null; + provider.packetService = null; + } + + private DeviceEvent deviceEvent(DeviceEvent.Type type, DeviceId did) { + return new DeviceEvent(type, deviceService.getDevice(did)); + + } + + @SuppressWarnings(value = { "unused" }) + private DeviceEvent portEvent(DeviceEvent.Type type, DeviceId did, PortNumber port) { + return new DeviceEvent(type, deviceService.getDevice(did), + deviceService.getPort(did, port)); + } + + private DeviceEvent portEvent(DeviceEvent.Type type, DeviceId did, Port port) { + return new DeviceEvent(type, deviceService.getDevice(did), port); + } + + private Port port(DeviceId did, long port, boolean enabled) { + return new DefaultPort(deviceService.getDevice(did), + PortNumber.portNumber(port), enabled); + } + + + private boolean vanishedDpid(DeviceId... dids) { + for (int i = 0; i < dids.length; i++) { + if (!providerService.vanishedDpid.contains(dids[i])) { + return false; + } + } + return true; + } + + private boolean vanishedPort(Long... ports) { + for (int i = 0; i < ports.length; i++) { + if (!providerService.vanishedPort.contains(ports[i])) { + return false; + } + } + return true; + } + + private boolean detectedLink(DeviceId src, DeviceId dst) { + for (DeviceId key : providerService.discoveredLinks.keySet()) { + if (key.equals(src)) { + return providerService.discoveredLinks.get(src).equals(dst); + } + } + return false; + } + + + private class TestLinkRegistry implements LinkProviderRegistry { + + @Override + public LinkProviderService register(LinkProvider provider) { + providerService = new TestLinkProviderService(provider); + return providerService; + } + + @Override + public void unregister(LinkProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + } + + private class TestLinkProviderService + extends AbstractProviderService<LinkProvider> + implements LinkProviderService { + + List<DeviceId> vanishedDpid = Lists.newLinkedList(); + List<Long> vanishedPort = Lists.newLinkedList(); + Map<DeviceId, DeviceId> discoveredLinks = Maps.newHashMap(); + + protected TestLinkProviderService(LinkProvider provider) { + super(provider); + } + + @Override + public void linkDetected(LinkDescription linkDescription) { + DeviceId sDid = linkDescription.src().deviceId(); + DeviceId dDid = linkDescription.dst().deviceId(); + discoveredLinks.put(sDid, dDid); + } + + @Override + public void linkVanished(LinkDescription linkDescription) { + } + + @Override + public void linksVanished(ConnectPoint connectPoint) { + vanishedPort.add(connectPoint.port().toLong()); + + } + + @Override + public void linksVanished(DeviceId deviceId) { + vanishedDpid.add(deviceId); + } + + + } + + + + private class TestPacketContext implements PacketContext { + + protected Device device; + protected boolean blocked = false; + + public TestPacketContext(Device dev) { + device = dev; + } + + @Override + public long time() { + return 0; + } + + @Override + public InboundPacket inPacket() { + ONOSLLDP lldp = new ONOSLLDP(); + lldp.setChassisId(device.chassisId()); + lldp.setPortId((int) pd1.number().toLong()); + lldp.setDevice(deviceService.getDevice(DID1).id().toString()); + + + Ethernet ethPacket = new Ethernet(); + ethPacket.setEtherType(Ethernet.TYPE_LLDP); + ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA); + ethPacket.setPayload(lldp); + ethPacket.setPad(true); + + + + ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11"); + + ConnectPoint cp = new ConnectPoint(device.id(), pd3.number()); + + return new DefaultInboundPacket(cp, ethPacket, + ByteBuffer.wrap(ethPacket.serialize())); + + } + + @Override + public OutboundPacket outPacket() { + return null; + } + + @Override + public TrafficTreatment.Builder treatmentBuilder() { + return null; + } + + @Override + public void send() { + + } + + @Override + public boolean block() { + blocked = true; + return blocked; + } + + @Override + public boolean isHandled() { + return blocked; + } + + } + + private class TestPacketService extends PacketServiceAdapter { + @Override + public void addProcessor(PacketProcessor processor, int priority) { + testProcessor = processor; + } + } + + private class TestDeviceService extends DeviceServiceAdapter { + + private Map<DeviceId, Device> devices = new HashMap<>(); + private final ArrayListMultimap<DeviceId, Port> ports = + ArrayListMultimap.create(); + + public TestDeviceService() { + Device d1 = new DefaultDevice(ProviderId.NONE, DID1, Device.Type.SWITCH, + "TESTMF", "TESTHW", "TESTSW", "TESTSN", new ChassisId()); + Device d2 = new DefaultDevice(ProviderId.NONE, DID2, Device.Type.SWITCH, + "TESTMF", "TESTHW", "TESTSW", "TESTSN", new ChassisId()); + devices.put(DID1, d1); + devices.put(DID2, d2); + + pd1 = new DefaultPort(d1, PortNumber.portNumber(1), true); + pd2 = new DefaultPort(d1, PortNumber.portNumber(2), true); + pd3 = new DefaultPort(d2, PortNumber.portNumber(1), true); + pd4 = new DefaultPort(d2, PortNumber.portNumber(2), true); + + ports.putAll(DID1, Lists.newArrayList(pd1, pd2)); + ports.putAll(DID2, Lists.newArrayList(pd3, pd4)); + } + + @Override + public int getDeviceCount() { + return devices.values().size(); + } + + @Override + public Iterable<Device> getDevices() { + return Collections.emptyList(); + } + + @Override + public Device getDevice(DeviceId deviceId) { + return devices.get(deviceId); + } + + @Override + public MastershipRole getRole(DeviceId deviceId) { + return MastershipRole.MASTER; + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + return ports.get(deviceId); + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + for (Port p : ports.get(deviceId)) { + if (p.number().equals(portNumber)) { + return p; + } + } + return null; + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + return true; + } + + @Override + public void addListener(DeviceListener listener) { + deviceListener = listener; + + } + + @Override + public void removeListener(DeviceListener listener) { + + } + } + + private final class TestMasterShipService implements MastershipService { + + @Override + public MastershipRole getLocalRole(DeviceId deviceId) { + return MastershipRole.MASTER; + } + + @Override + public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) { + return null; + } + + @Override + public NodeId getMasterFor(DeviceId deviceId) { + return null; + } + + @Override + public Set<DeviceId> getDevicesOf(NodeId nodeId) { + return null; + } + + @Override + public void addListener(MastershipListener listener) { + + } + + @Override + public void removeListener(MastershipListener listener) { + + } + + @Override + public RoleInfo getNodesFor(DeviceId deviceId) { + return new RoleInfo(new NodeId("foo"), Collections.<NodeId>emptyList()); + } + } + + +} diff --git a/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesStoreTest.java b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesStoreTest.java new file mode 100644 index 00000000..0ac31123 --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesStoreTest.java @@ -0,0 +1,80 @@ +/* + * 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.lldp.impl; + +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.onosproject.net.Device; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; + +public class SuppressionRulesStoreTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + // "lldp_suppression.json" + SuppressionRules testData + = new SuppressionRules(ImmutableSet.of(deviceId("of:2222000000000000")), + ImmutableSet.of(Device.Type.ROADM), + ImmutableMap.of("no-lldp", SuppressionRules.ANY_VALUE, + "sendLLDP", "false")); + + private static void assertRulesEqual(SuppressionRules expected, SuppressionRules actual) { + assertEquals(expected.getSuppressedDevice(), + actual.getSuppressedDevice()); + assertEquals(expected.getSuppressedDeviceType(), + actual.getSuppressedDeviceType()); + assertEquals(expected.getSuppressedAnnotation(), + actual.getSuppressedAnnotation()); + } + + @Test + public void testRead() throws URISyntaxException, IOException { + Path path = Paths.get(Resources.getResource("lldp_suppression.json").toURI()); + + SuppressionRulesStore store = new SuppressionRulesStore(path.toString()); + + SuppressionRules rules = store.read(); + + assertRulesEqual(testData, rules); + } + + @Test + public void testWrite() throws IOException { + File newFile = tempFolder.newFile(); + SuppressionRulesStore store = new SuppressionRulesStore(newFile); + store.write(testData); + + SuppressionRulesStore reload = new SuppressionRulesStore(newFile); + SuppressionRules rules = reload.read(); + + assertRulesEqual(testData, rules); + } +} diff --git a/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java new file mode 100644 index 00000000..52f0bb1e --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java @@ -0,0 +1,147 @@ +package org.onosproject.provider.lldp.impl; + +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onosproject.net.Annotations; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class SuppressionRulesTest { + + private static final DeviceId NON_SUPPRESSED_DID = deviceId("of:1111000000000000"); + private static final DeviceId SUPPRESSED_DID = deviceId("of:2222000000000000"); + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final String MFR = "whitebox"; + private static final String HW = "1.1.x"; + private static final String SW1 = "3.8.1"; + private static final String SN = "43311-12345"; + private static final ChassisId CID = new ChassisId(); + + private static final PortNumber P1 = PortNumber.portNumber(1); + + private SuppressionRules rules; + + @Before + public void setUp() throws Exception { + rules = new SuppressionRules(ImmutableSet.of(SUPPRESSED_DID), + ImmutableSet.of(Device.Type.ROADM), + ImmutableMap.of("no-lldp", SuppressionRules.ANY_VALUE, + "sendLLDP", "false")); + } + + @Test + public void testSuppressedDeviceId() { + Device device = new DefaultDevice(PID, + SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedDeviceType() { + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.ROADM, + MFR, HW, SW1, SN, CID); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedDeviceAnnotation() { + Annotations annotation = DefaultAnnotations.builder() + .set("no-lldp", "random") + .build(); + + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID, annotation); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedDeviceAnnotationExact() { + Annotations annotation = DefaultAnnotations.builder() + .set("sendLLDP", "false") + .build(); + + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID, annotation); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testNotSuppressedDevice() { + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + assertFalse(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedPortOnSuppressedDevice() { + Device device = new DefaultDevice(PID, + SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true); + + assertTrue(rules.isSuppressed(port)); + } + + @Test + public void testSuppressedPortAnnotation() { + Annotations annotation = DefaultAnnotations.builder() + .set("no-lldp", "random") + .build(); + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true, annotation); + + assertTrue(rules.isSuppressed(port)); + } + + @Test + public void testSuppressedPortAnnotationExact() { + Annotations annotation = DefaultAnnotations.builder() + .set("sendLLDP", "false") + .build(); + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true, annotation); + + assertTrue(rules.isSuppressed(port)); + } + + @Test + public void testNotSuppressedPort() { + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true); + + assertFalse(rules.isSuppressed(port)); + } +} diff --git a/framework/src/onos/providers/lldp/src/test/resources/lldp_suppression.json b/framework/src/onos/providers/lldp/src/test/resources/lldp_suppression.json new file mode 100644 index 00000000..062e73aa --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/resources/lldp_suppression.json @@ -0,0 +1,6 @@ +{ + "deviceId" : [ "of:2222000000000000" ], + "deviceType" : [ "ROADM" ], + "annotation" : { "no-lldp" : null, "sendLLDP" : "false" } +} + |