| /** |
| * Copyright 2011, Big Switch Networks, Inc. |
| * Originally created by David Erickson, Stanford University |
| * |
| * 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 net.onrc.onos.core.linkdiscovery; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import net.floodlightcontroller.core.FloodlightContext; |
| import net.floodlightcontroller.core.IFloodlightProviderService; |
| import net.floodlightcontroller.core.IOFMessageListener; |
| import net.floodlightcontroller.core.IOFSwitch; |
| import net.floodlightcontroller.core.IOFSwitch.PortChangeType; |
| import net.floodlightcontroller.core.IOFSwitchListener; |
| import net.floodlightcontroller.core.IUpdate; |
| import net.floodlightcontroller.core.annotations.LogMessageCategory; |
| import net.floodlightcontroller.core.annotations.LogMessageDoc; |
| import net.floodlightcontroller.core.annotations.LogMessageDocs; |
| import net.floodlightcontroller.core.module.FloodlightModuleContext; |
| import net.floodlightcontroller.core.module.FloodlightModuleException; |
| import net.floodlightcontroller.core.module.IFloodlightModule; |
| import net.floodlightcontroller.core.module.IFloodlightService; |
| import net.floodlightcontroller.core.util.SingletonTask; |
| import net.floodlightcontroller.restserver.IRestApiService; |
| import net.floodlightcontroller.threadpool.IThreadPoolService; |
| import net.onrc.onos.core.linkdiscovery.web.LinkDiscoveryWebRoutable; |
| import net.onrc.onos.core.packet.Ethernet; |
| import net.onrc.onos.core.packet.LLDP; |
| import net.onrc.onos.core.packet.OnosLldp; |
| import net.onrc.onos.core.registry.IControllerRegistryService; |
| import net.onrc.onos.core.util.SwitchPort; |
| |
| import org.projectfloodlight.openflow.protocol.OFFactory; |
| import org.projectfloodlight.openflow.protocol.OFMessage; |
| import org.projectfloodlight.openflow.protocol.OFPacketIn; |
| import org.projectfloodlight.openflow.protocol.OFPacketOut; |
| import org.projectfloodlight.openflow.protocol.OFPortConfig; |
| import org.projectfloodlight.openflow.protocol.OFPortDesc; |
| import org.projectfloodlight.openflow.protocol.OFPortReason; |
| import org.projectfloodlight.openflow.protocol.OFPortState; |
| import org.projectfloodlight.openflow.protocol.OFPortStatus; |
| import org.projectfloodlight.openflow.protocol.OFType; |
| import org.projectfloodlight.openflow.protocol.action.OFAction; |
| import org.projectfloodlight.openflow.types.OFBufferId; |
| import org.projectfloodlight.openflow.types.OFPort; |
| import org.projectfloodlight.openflow.util.HexString; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Discovers links between OpenFlow switches. |
| * <p> |
| * Discovery is performed by sending probes (LLDP packets) over the links in the |
| * data plane. The LinkDiscoveryManager sends probes periodically on all ports |
| * on all connected switches. The probes contain the sending switch's DPID and |
| * outgoing port number. LLDP packets that are received (via an OpenFlow |
| * packet-in) indicate there is a link between the receiving port and the |
| * sending port, which was encoded in the LLDP. When the LinkDiscoveryManager |
| * observes a new link, a Link object is created and an event is fired for any |
| * event listeners. |
| * </p> |
| * Links are removed for one of three reasons: |
| * <ul> |
| * <li>A probe has not been received on the link for an interval (the timeout |
| * interval)</li> |
| * <li>The port went down or was disabled (as observed by OpenFlow port-status |
| * messages) or the disconnection of the switch</li> |
| * <li>Link discovery was explicitly disabled on a port with the |
| * {@link #disableDiscoveryOnPort(long, short)} method</li> |
| * </ul> |
| * When the LinkDiscoveryManager removes a link it also fires an event for the |
| * listeners. |
| */ |
| @LogMessageCategory("Network Topology") |
| public class LinkDiscoveryManager implements IOFMessageListener, IOFSwitchListener, |
| ILinkDiscoveryService, IFloodlightModule { |
| |
| private static final Logger log = |
| LoggerFactory.getLogger(LinkDiscoveryManager.class); |
| |
| private IFloodlightProviderService controller; |
| |
| private IFloodlightProviderService floodlightProvider; |
| private IThreadPoolService threadPool; |
| private IRestApiService restApi; |
| private IControllerRegistryService registryService; |
| |
| // LLDP fields |
| static final byte[] LLDP_STANDARD_DST_MAC_STRING = |
| HexString.fromHexString("01:80:c2:00:00:0e"); |
| private static final long LINK_LOCAL_MASK = 0xfffffffffff0L; |
| private static final long LINK_LOCAL_VALUE = 0x0180c2000000L; |
| |
| // Link discovery task details. |
| private SingletonTask discoveryTask; |
| private static final int DISCOVERY_TASK_INTERVAL = 1; |
| private static final int LINK_TIMEOUT = 35; // original 35 secs, aggressive |
| // 5 secs |
| private static final int LLDP_TO_ALL_INTERVAL = 15; // original 15 seconds, |
| // aggressive 2 secs. |
| private long lldpClock = 0; |
| // This value is intentionally kept higher than LLDP_TO_ALL_INTERVAL. |
| // If we want to identify link failures faster, we could decrease this |
| // value to a small number, say 1 or 2 sec. |
| private static final int LLDP_TO_KNOWN_INTERVAL = 20; // LLDP frequency for |
| // known links |
| |
| private ReentrantReadWriteLock lock; |
| |
| /** |
| * Map from link to the most recent time it was verified functioning. |
| */ |
| protected Map<Link, LinkInfo> links; |
| |
| /** |
| * Map from switch id to a set of all links with it as an endpoint. |
| */ |
| protected Map<Long, Set<Link>> switchLinks; |
| |
| /** |
| * Map from a id:port to the set of links containing it as an endpoint. |
| */ |
| protected Map<NodePortTuple, Set<Link>> portLinks; |
| |
| /** |
| * Listeners are called in the order they were added to the the list. |
| */ |
| private final List<ILinkDiscoveryListener> linkDiscoveryListeners = |
| new CopyOnWriteArrayList<>(); |
| |
| /** |
| * List of ports through which LLDPs are not sent. |
| */ |
| private Set<NodePortTuple> suppressLinkDiscovery; |
| |
| private enum UpdateType { |
| LINK_ADDED, |
| LINK_REMOVED |
| } |
| |
| private class LinkUpdate implements IUpdate { |
| private final Link link; |
| private final UpdateType operation; |
| |
| public LinkUpdate(Link link, UpdateType operation) { |
| this.link = link; |
| this.operation = operation; |
| } |
| |
| @Override |
| public void dispatch() { |
| if (log.isTraceEnabled()) { |
| log.trace("Dispatching link discovery update {} for {}", |
| operation, link); |
| } |
| for (ILinkDiscoveryListener listener : linkDiscoveryListeners) { |
| switch (operation) { |
| case LINK_ADDED: |
| listener.linkAdded(link); |
| break; |
| case LINK_REMOVED: |
| listener.linkRemoved(link); |
| break; |
| default: |
| log.warn("Unknown link update operation {}", operation); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Gets the LLDP sending period in seconds. |
| * |
| * @return LLDP sending period in seconds. |
| */ |
| public int getLldpFrequency() { |
| return LLDP_TO_KNOWN_INTERVAL; |
| } |
| |
| /** |
| * Gets the LLDP timeout value in seconds. |
| * |
| * @return LLDP timeout value in seconds |
| */ |
| public int getLldpTimeout() { |
| return LINK_TIMEOUT; |
| } |
| |
| @Override |
| public Set<NodePortTuple> getDiscoveryDisabledPorts() { |
| return suppressLinkDiscovery; |
| } |
| |
| @Override |
| public void disableDiscoveryOnPort(long sw, short port) { |
| NodePortTuple npt = new NodePortTuple(sw, port); |
| this.suppressLinkDiscovery.add(npt); |
| deleteLinksOnPort(npt); |
| } |
| |
| @Override |
| public void enableDiscoveryOnPort(long sw, short port) { |
| NodePortTuple npt = new NodePortTuple(sw, port); |
| this.suppressLinkDiscovery.remove(npt); |
| discover(npt); |
| } |
| |
| private boolean isLinkDiscoverySuppressed(long sw, short p) { |
| return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, p)); |
| } |
| |
| private void discoverLinks() { |
| |
| // time out known links. |
| timeOutLinks(); |
| |
| // increment LLDP clock |
| lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL; |
| |
| if (lldpClock == 0) { |
| log.debug("Sending LLDP out on all ports."); |
| discoverOnAllPorts(); |
| } |
| } |
| |
| /** |
| * Send LLDP on known ports. |
| */ |
| protected void discoverOnKnownLinkPorts() { |
| // Copy the port set. |
| Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>(); |
| nptSet.addAll(portLinks.keySet()); |
| |
| // Send LLDP from each of them. |
| for (NodePortTuple npt : nptSet) { |
| discover(npt); |
| } |
| } |
| |
| private void discover(NodePortTuple npt) { |
| discover(npt.getNodeId(), npt.getPortId()); |
| } |
| |
| private void discover(long sw, short port) { |
| sendDiscoveryMessage(sw, port, false); |
| } |
| |
| /** |
| * Send link discovery message out of a given switch port. The discovery |
| * message is a standard LLDP containing ONOS-specific TLVs. |
| * |
| * @param sw the switch to send on |
| * @param port the port to send out |
| * @param isReverse indicates whether the LLDP was sent as a response |
| */ |
| @LogMessageDoc(level = "ERROR", |
| message = "Failure sending LLDP out port {port} on switch {switch}", |
| explanation = "An I/O error occured while sending LLDP message " + |
| "to the switch.", |
| recommendation = LogMessageDoc.CHECK_SWITCH) |
| protected void sendDiscoveryMessage(long sw, short port, |
| boolean isReverse) { |
| |
| IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); |
| if (iofSwitch == null) { |
| return; |
| } |
| |
| if (port == OFPort.LOCAL.getShortPortNumber()) { |
| return; |
| } |
| |
| OFPortDesc ofpPort = iofSwitch.getPort(port); |
| |
| if (ofpPort == null) { |
| if (log.isTraceEnabled()) { |
| log.trace("Null physical port. sw={}, port={}", sw, port); |
| } |
| return; |
| } |
| |
| if (isLinkDiscoverySuppressed(sw, port)) { |
| // Don't send LLDPs out of this port as suppressLLDPs set |
| return; |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Sending LLDP packet out of swich: {}, port: {}", |
| sw, port); |
| } |
| |
| OFFactory factory = iofSwitch.getFactory(); |
| OFPacketOut po = createLLDPPacketOut(sw, ofpPort, isReverse, factory); |
| |
| try { |
| iofSwitch.write(po, null); |
| iofSwitch.flush(); |
| } catch (IOException e) { |
| log.error("Failure sending LLDP out port " + port + " on switch " |
| + iofSwitch.getStringId(), e); |
| } |
| |
| } |
| |
| /** |
| * Creates packet_out LLDP for specified output port. |
| * |
| * @param dpid the dpid of the outgoing switch |
| * @param port the outgoing port |
| * @param isReverse whether this is a reverse LLDP or not |
| * @param factory the factory to use to create the message |
| * @return Packet_out message with LLDP data |
| */ |
| private OFPacketOut createLLDPPacketOut(long dpid, |
| final OFPortDesc port, boolean isReverse, OFFactory factory) { |
| // Set up packets |
| // TODO optimize by not creating new packets each time |
| OnosLldp lldpPacket = new OnosLldp(); |
| |
| Ethernet ethPacket = new Ethernet(); |
| ethPacket.setEtherType(Ethernet.TYPE_LLDP); |
| ethPacket.setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING); |
| ethPacket.setPayload(lldpPacket); |
| ethPacket.setPad(true); |
| |
| lldpPacket.setSwitch(dpid); |
| lldpPacket.setPort(port.getPortNo().getShortPortNumber()); |
| lldpPacket.setReverse(isReverse); |
| ethPacket.setSourceMACAddress(port.getHwAddr().getBytes()); |
| final byte[] lldp = ethPacket.serialize(); |
| |
| List<OFAction> actions = new ArrayList<OFAction>(); |
| actions.add(factory.actions() |
| .buildOutput() |
| .setPort(OFPort.ofShort(port.getPortNo().getShortPortNumber())) |
| .build()); |
| OFPacketOut po = factory.buildPacketOut() |
| .setData(lldp) |
| .setBufferId(OFBufferId.NO_BUFFER) |
| .setInPort(OFPort.CONTROLLER) |
| .setActions(actions) |
| .build(); |
| |
| return po; |
| } |
| |
| /** |
| * Send LLDPs to all switch-ports. |
| */ |
| protected void discoverOnAllPorts() { |
| if (log.isTraceEnabled()) { |
| log.trace("Sending LLDP packets out of all the enabled ports on switch"); |
| } |
| |
| for (IOFSwitch sw : floodlightProvider.getSwitches().values()) { |
| if (sw.getEnabledPorts() == null) { |
| continue; |
| } |
| for (OFPortDesc ofp : sw.getEnabledPorts()) { |
| if (isLinkDiscoverySuppressed(sw.getId(), |
| ofp.getPortNo().getShortPortNumber())) { |
| continue; |
| } |
| |
| sendDiscoveryMessage(sw.getId(), |
| ofp.getPortNo().getShortPortNumber(), false); |
| } |
| } |
| } |
| |
| @Override |
| public String getName() { |
| return "linkdiscovery"; |
| } |
| |
| @Override |
| public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { |
| switch (msg.getType()) { |
| case PACKET_IN: |
| if (msg instanceof OFPacketIn) { |
| return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, |
| cntx); |
| } |
| break; |
| case PORT_STATUS: |
| if (msg instanceof OFPortStatus) { |
| return this.handlePortStatus(sw, (OFPortStatus) msg); |
| } |
| break; |
| default: |
| break; |
| } |
| return Command.CONTINUE; |
| } |
| |
| protected Command handleLldp(LLDP lldp, long sw, OFPacketIn pi, short inport) { |
| // If LLDP is suppressed on this port, ignore received packet as well |
| IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); |
| if (iofSwitch == null) { |
| return Command.STOP; |
| } |
| |
| if (isLinkDiscoverySuppressed(sw, inport)) { |
| return Command.STOP; |
| } |
| |
| // If this is a malformed LLDP, or not from us, exit |
| if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) { |
| return Command.CONTINUE; |
| } |
| |
| // Verify this LLDP packet matches what we're looking for |
| byte[] packetData = pi.getData(); |
| if (!OnosLldp.isOnosLldp(packetData)) { |
| log.trace("Dropping LLDP that wasn't sent by ONOS"); |
| return Command.STOP; |
| } |
| |
| SwitchPort switchPort = OnosLldp.extractSwitchPort(packetData); |
| long remoteDpid = switchPort.dpid().value(); |
| short remotePort = switchPort.port().shortValue(); |
| IOFSwitch remoteSwitch = floodlightProvider.getSwitches().get( |
| switchPort.dpid().value()); |
| |
| OFPortDesc physicalPort = null; |
| if (remoteSwitch != null) { |
| physicalPort = remoteSwitch.getPort(remotePort); |
| if (!remoteSwitch.portEnabled(remotePort)) { |
| if (log.isTraceEnabled()) { |
| log.trace("Ignoring link with disabled source port: " + |
| "switch {} port {}", remoteSwitch, remotePort); |
| } |
| return Command.STOP; |
| } |
| if (suppressLinkDiscovery.contains( |
| new NodePortTuple(remoteSwitch.getId(), remotePort))) { |
| if (log.isTraceEnabled()) { |
| log.trace("Ignoring link with suppressed src port: " + |
| "switch {} port {}", remoteSwitch, remotePort); |
| } |
| return Command.STOP; |
| } |
| } |
| if (!iofSwitch.portEnabled(inport)) { |
| if (log.isTraceEnabled()) { |
| log.trace("Ignoring link with disabled dest port: " + |
| "switch {} port {}", sw, inport); |
| } |
| return Command.STOP; |
| } |
| |
| // TODO It probably should be empty Set instead of null. Confirm and fix. |
| Set<OFPortState> srcPortState = (physicalPort != null) |
| ? physicalPort.getState() : null; |
| physicalPort = iofSwitch.getPort(inport); |
| Set<OFPortState> dstPortState = (physicalPort != null) |
| ? physicalPort.getState() : null; |
| |
| // Store the time of update to this link, and push it out to |
| // routingEngine |
| Link lt = new Link(remoteDpid, remotePort, iofSwitch.getId(), inport); |
| |
| LinkInfo linkInfo = new LinkInfo(System.currentTimeMillis(), |
| System.currentTimeMillis(), srcPortState, dstPortState); |
| |
| addOrUpdateLink(lt, linkInfo); |
| |
| // Check if reverse link exists. |
| // If it doesn't exist and if the forward link was seen |
| // first seen within a small interval, send probe on the |
| // reverse link. |
| boolean isReverse = OnosLldp.isReverse(lldp); |
| |
| LinkInfo newLinkInfo = links.get(lt); |
| if (newLinkInfo != null && !isReverse) { |
| Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), |
| lt.getSrc(), lt.getSrcPort()); |
| LinkInfo reverseInfo = links.get(reverseLink); |
| if (reverseInfo == null) { |
| // the reverse link does not exist. |
| if (newLinkInfo.getFirstSeenTime() > System.currentTimeMillis() |
| - LINK_TIMEOUT) { |
| this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), true); |
| } |
| } |
| } |
| |
| // Consume this message |
| return Command.STOP; |
| } |
| |
| protected Command handlePacketIn(long sw, OFPacketIn pi, |
| FloodlightContext cntx) { |
| Ethernet eth = |
| IFloodlightProviderService.bcStore.get(cntx, |
| IFloodlightProviderService.CONTEXT_PI_PAYLOAD); |
| short inport = (short) cntx.getStorage() |
| .get(IFloodlightProviderService.CONTEXT_PI_INPORT); |
| |
| if (eth.getEtherType() == Ethernet.TYPE_LLDP) { |
| return handleLldp((LLDP) eth.getPayload(), sw, pi, inport); |
| } else if (eth.getEtherType() < 1500) { |
| long destMac = eth.getDestinationMAC().toLong(); |
| if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE) { |
| if (log.isTraceEnabled()) { |
| log.trace("Ignoring packet addressed to 802.1D/Q " + |
| "reserved address."); |
| } |
| return Command.STOP; |
| } |
| } |
| |
| return Command.CONTINUE; |
| } |
| |
| protected void addOrUpdateLink(Link lt, LinkInfo detectedLinkInfo) { |
| lock.writeLock().lock(); |
| try { |
| LinkInfo existingInfo = links.get(lt); |
| |
| LinkInfo newLinkInfo = new LinkInfo( |
| ((existingInfo == null) ? detectedLinkInfo.getFirstSeenTime() |
| : existingInfo.getFirstSeenTime()), |
| detectedLinkInfo.getLastProbeReceivedTime(), |
| detectedLinkInfo.getSrcPortState(), |
| detectedLinkInfo.getDstPortState()); |
| |
| // Add new LinkInfo or update old LinkInfo |
| links.put(lt, newLinkInfo); |
| |
| if (log.isTraceEnabled()) { |
| log.trace("addOrUpdateLink: {}", lt); |
| } |
| |
| NodePortTuple srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); |
| NodePortTuple dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); |
| |
| // If this is the first time we've seen the link, add the Link |
| // object to the data structures/indexes as well |
| if (existingInfo == null) { |
| log.trace("Creating new Link: {}", lt); |
| // index it by switch source |
| if (!switchLinks.containsKey(lt.getSrc())) { |
| switchLinks.put(lt.getSrc(), new HashSet<Link>()); |
| } |
| switchLinks.get(lt.getSrc()).add(lt); |
| |
| // index it by switch dest |
| if (!switchLinks.containsKey(lt.getDst())) { |
| switchLinks.put(lt.getDst(), new HashSet<Link>()); |
| } |
| switchLinks.get(lt.getDst()).add(lt); |
| |
| // index both ends by switch:port |
| if (!portLinks.containsKey(srcNpt)) { |
| portLinks.put(srcNpt, new HashSet<Link>()); |
| } |
| portLinks.get(srcNpt).add(lt); |
| |
| if (!portLinks.containsKey(dstNpt)) { |
| portLinks.put(dstNpt, new HashSet<Link>()); |
| } |
| portLinks.get(dstNpt).add(lt); |
| |
| // Publish LINK_ADDED event |
| controller.publishUpdate(new LinkUpdate(lt, UpdateType.LINK_ADDED)); |
| } |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Removes links from the data structures. |
| * |
| * @param linksToDelete the list of links to delete |
| */ |
| protected void deleteLinks(List<Link> linksToDelete) { |
| lock.writeLock().lock(); |
| try { |
| for (Link lt : linksToDelete) { |
| NodePortTuple srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); |
| NodePortTuple dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); |
| |
| switchLinks.get(lt.getSrc()).remove(lt); |
| switchLinks.get(lt.getDst()).remove(lt); |
| if (switchLinks.containsKey(lt.getSrc()) && |
| switchLinks.get(lt.getSrc()).isEmpty()) { |
| this.switchLinks.remove(lt.getSrc()); |
| } |
| if (this.switchLinks.containsKey(lt.getDst()) && |
| this.switchLinks.get(lt.getDst()).isEmpty()) { |
| this.switchLinks.remove(lt.getDst()); |
| } |
| |
| if (this.portLinks.get(srcNpt) != null) { |
| this.portLinks.get(srcNpt).remove(lt); |
| if (this.portLinks.get(srcNpt).isEmpty()) { |
| this.portLinks.remove(srcNpt); |
| } |
| } |
| if (this.portLinks.get(dstNpt) != null) { |
| this.portLinks.get(dstNpt).remove(lt); |
| if (this.portLinks.get(dstNpt).isEmpty()) { |
| this.portLinks.remove(dstNpt); |
| } |
| } |
| |
| this.links.remove(lt); |
| |
| controller.publishUpdate(new LinkUpdate(lt, |
| UpdateType.LINK_REMOVED)); |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Deleted link {}", lt); |
| } |
| } |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Handles an OFPortStatus message from a switch. We will add or delete |
| * LinkTupes as well re-compute the topology if needed. |
| * |
| * @param sw The dpid of the switch that sent the port status message |
| * @param ps The OFPortStatus message |
| * @return The Command to continue or stop after we process this message |
| */ |
| protected Command handlePortStatus(IOFSwitch sw, OFPortStatus ps) { |
| |
| // If we do not control this switch, then we should not process its |
| // port status messages |
| if (!registryService.hasControl(sw.getId())) { |
| return Command.CONTINUE; |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("handlePortStatus: Switch {} port #{} reason {}; " + |
| "config is {} state is {}", |
| new Object[] {sw.getStringId(), |
| ps.getDesc().getPortNo(), |
| ps.getReason(), |
| ps.getDesc().getConfig(), |
| ps.getDesc().getState()}); |
| } |
| |
| short port = ps.getDesc().getPortNo().getShortPortNumber(); |
| NodePortTuple npt = new NodePortTuple(sw.getId(), port); |
| boolean linkDeleted = false; |
| boolean linkInfoChanged = false; |
| |
| lock.writeLock().lock(); |
| try { |
| // if ps is a delete, or a modify where the port is down or |
| // configured down |
| if (OFPortReason.DELETE == ps.getReason() || |
| (OFPortReason.MODIFY == ps.getReason() && |
| !portEnabled(ps.getDesc()))) { |
| deleteLinksOnPort(npt); |
| linkDeleted = true; |
| } else if (ps.getReason() == OFPortReason.MODIFY) { |
| // If ps is a port modification and the port state has changed |
| // that affects links in the topology |
| |
| if (this.portLinks.containsKey(npt)) { |
| for (Link lt : this.portLinks.get(npt)) { |
| LinkInfo linkInfo = links.get(lt); |
| assert (linkInfo != null); |
| LinkInfo newLinkInfo = null; |
| |
| if (lt.isSrcPort(npt) && |
| !linkInfo.getSrcPortState().equals( |
| ps.getDesc().getState())) { |
| // If this port status is for the src port and the |
| // port state has changed, create a new link info |
| // with the new state |
| |
| newLinkInfo = new LinkInfo(linkInfo.getFirstSeenTime(), |
| linkInfo.getLastProbeReceivedTime(), |
| ps.getDesc().getState(), |
| linkInfo.getDstPortState()); |
| } else if (lt.isDstPort(npt) && |
| !linkInfo.getDstPortState().equals( |
| ps.getDesc().getState())) { |
| // If this port status is for the dst port and the |
| // port state has changed, create a new link info |
| // with the new state |
| |
| newLinkInfo = new LinkInfo(linkInfo.getFirstSeenTime(), |
| linkInfo.getLastProbeReceivedTime(), |
| linkInfo.getSrcPortState(), |
| ps.getDesc().getState()); |
| } |
| |
| if (newLinkInfo != null) { |
| linkInfoChanged = true; |
| links.put(lt, newLinkInfo); |
| } |
| } |
| } |
| } |
| |
| if (!linkDeleted && !linkInfoChanged) { |
| if (log.isTraceEnabled()) { |
| log.trace("handlePortStatus: Switch {} port #{} reason {};" + |
| " no links to update/remove", |
| new Object[] {HexString.toHexString(sw.getId()), |
| ps.getDesc().getPortNo(), |
| ps.getReason()}); |
| } |
| } |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| |
| if (!linkDeleted) { |
| // Send LLDP right away when port state is changed for faster |
| // cluster-merge. If it is a link delete then there is not need |
| // to send the LLDPs right away and instead we wait for the LLDPs |
| // to be sent on the timer as it is normally done |
| // do it outside the write-lock |
| // sendLLDPTask.reschedule(1000, TimeUnit.MILLISECONDS); |
| processNewPort(npt.getNodeId(), npt.getPortId()); |
| } |
| return Command.CONTINUE; |
| } |
| |
| /** |
| * Process a new port. If link discovery is disabled on the port, then do |
| * nothing. Otherwise, send LLDP message. |
| * |
| * @param sw the dpid of the switch the port is on |
| * @param p the number of the port |
| */ |
| private void processNewPort(long sw, int p) { |
| if (isLinkDiscoverySuppressed(sw, (short) p)) { |
| // Do nothing as link discovery is suppressed. |
| return; |
| } else { |
| discover(sw, (short) p); |
| } |
| } |
| |
| /** |
| * We send out LLDP messages when a switch is added to discover the |
| * topology. |
| * |
| * @param swId the datapath Id of the new switch |
| */ |
| @Override |
| public void switchActivatedMaster(long swId) { |
| IOFSwitch sw = floodlightProvider.getSwitch(swId); |
| if (sw == null) { |
| log.warn("Added switch not available {} ", swId); |
| return; |
| } |
| if (sw.getEnabledPorts() != null) { |
| for (Integer p : sw.getEnabledPortNumbers()) { |
| processNewPort(swId, p); |
| } |
| } |
| } |
| |
| /** |
| * When a switch disconnects we remove any links from our map and notify. |
| * |
| * @param swId the datapath Id of the switch that was removed |
| */ |
| @Override |
| public void switchDisconnected(long swId) { |
| IOFSwitch sw = floodlightProvider.getSwitch(swId); |
| if (sw == null) { |
| log.warn("Removed switch not available {} ", swId); |
| return; |
| } |
| // Update event history |
| |
| List<Link> eraseList = new ArrayList<Link>(); |
| lock.writeLock().lock(); |
| try { |
| if (switchLinks.containsKey(swId)) { |
| if (log.isTraceEnabled()) { |
| log.trace("Handle switchRemoved. Switch {}; removing links {}", |
| HexString.toHexString(swId), switchLinks.get(swId)); |
| } |
| // add all tuples with an endpoint on this switch to erase list |
| eraseList.addAll(switchLinks.get(swId)); |
| deleteLinks(eraseList); |
| } |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void switchActivatedEqual(long swId) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| @Override |
| public void switchMasterToEqual(long swId) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| @Override |
| public void switchEqualToMaster(long swId) { |
| // for now treat as switchActivatedMaster |
| switchActivatedMaster(swId); |
| } |
| |
| /* |
| * We don't react to port changed notifications here. we listen for |
| * OFPortStatus messages directly. Might consider using this notifier |
| * instead |
| */ |
| @Override |
| public void switchPortChanged(long swId, OFPortDesc port, |
| PortChangeType changeType) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| /** |
| * Delete links incident on a given switch port. |
| * |
| * @param npt the port to delete links on |
| */ |
| protected void deleteLinksOnPort(NodePortTuple npt) { |
| List<Link> eraseList = new ArrayList<Link>(); |
| if (this.portLinks.containsKey(npt)) { |
| if (log.isTraceEnabled()) { |
| log.trace("handlePortStatus: Switch {} port #{} " + |
| "removing links {}", |
| new Object[] {HexString.toHexString(npt.getNodeId()), |
| npt.getPortId(), |
| this.portLinks.get(npt)}); |
| } |
| eraseList.addAll(this.portLinks.get(npt)); |
| deleteLinks(eraseList); |
| } |
| } |
| |
| /** |
| * Iterates through the list of links and deletes if the last discovery |
| * message reception time exceeds timeout values. |
| */ |
| protected void timeOutLinks() { |
| List<Link> eraseList = new ArrayList<Link>(); |
| Long curTime = System.currentTimeMillis(); |
| |
| // reentrant required here because deleteLink also write locks |
| lock.writeLock().lock(); |
| try { |
| Iterator<Entry<Link, LinkInfo>> it = |
| this.links.entrySet().iterator(); |
| while (it.hasNext()) { |
| Entry<Link, LinkInfo> entry = it.next(); |
| LinkInfo info = entry.getValue(); |
| |
| if ((info.getLastProbeReceivedTime() + (1000L * LINK_TIMEOUT) |
| < curTime)) { |
| eraseList.add(entry.getKey()); |
| } |
| } |
| |
| deleteLinks(eraseList); |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| private boolean portEnabled(OFPortDesc port) { |
| if (port == null) { |
| return false; |
| } |
| if (port.getConfig().contains(OFPortConfig.PORT_DOWN)) { |
| return false; |
| } |
| if (port.getState().contains(OFPortState.LINK_DOWN)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public Map<Link, LinkInfo> getLinks() { |
| lock.readLock().lock(); |
| Map<Link, LinkInfo> result; |
| try { |
| result = new HashMap<Link, LinkInfo>(links); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| return result; |
| } |
| |
| @Override |
| public void addListener(ILinkDiscoveryListener listener) { |
| linkDiscoveryListeners.add(listener); |
| } |
| |
| @Override |
| public void removeListener(ILinkDiscoveryListener listener) { |
| linkDiscoveryListeners.remove(listener); |
| } |
| |
| @Override |
| public boolean isCallbackOrderingPrereq(OFType type, String name) { |
| return false; |
| } |
| |
| @Override |
| public boolean isCallbackOrderingPostreq(OFType type, String name) { |
| return false; |
| } |
| |
| // IFloodlightModule classes |
| |
| @Override |
| public Collection<Class<? extends IFloodlightService>> getModuleServices() { |
| Collection<Class<? extends IFloodlightService>> l = |
| new ArrayList<Class<? extends IFloodlightService>>(); |
| l.add(ILinkDiscoveryService.class); |
| return l; |
| } |
| |
| @Override |
| public Map<Class<? extends IFloodlightService>, IFloodlightService> |
| getServiceImpls() { |
| Map<Class<? extends IFloodlightService>, IFloodlightService> m = |
| new HashMap<>(); |
| m.put(ILinkDiscoveryService.class, this); |
| return m; |
| } |
| |
| @Override |
| public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { |
| Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(); |
| l.add(IFloodlightProviderService.class); |
| l.add(IThreadPoolService.class); |
| l.add(IRestApiService.class); |
| l.add(IControllerRegistryService.class); |
| return l; |
| } |
| |
| @Override |
| public void init(FloodlightModuleContext context) |
| throws FloodlightModuleException { |
| floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); |
| threadPool = context.getServiceImpl(IThreadPoolService.class); |
| restApi = context.getServiceImpl(IRestApiService.class); |
| registryService = context.getServiceImpl(IControllerRegistryService.class); |
| |
| this.lock = new ReentrantReadWriteLock(); |
| this.links = new HashMap<Link, LinkInfo>(); |
| this.portLinks = new HashMap<NodePortTuple, Set<Link>>(); |
| this.suppressLinkDiscovery = |
| Collections.synchronizedSet(new HashSet<NodePortTuple>()); |
| this.switchLinks = new HashMap<Long, Set<Link>>(); |
| } |
| |
| @Override |
| @LogMessageDocs({ |
| @LogMessageDoc(level = "ERROR", |
| message = "No storage source found.", |
| explanation = "Storage source was not initialized; cannot initialize " |
| + |
| "link discovery.", |
| recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), |
| @LogMessageDoc(level = "ERROR", |
| message = "Error in installing listener for " + |
| "switch config table {table}", |
| explanation = "Failed to install storage notification for the " + |
| "switch config table", |
| recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), |
| @LogMessageDoc(level = "ERROR", |
| message = "No storage source found.", |
| explanation = "Storage source was not initialized; cannot initialize " |
| + |
| "link discovery.", |
| recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), |
| @LogMessageDoc(level = "ERROR", |
| message = "Exception in LLDP send timer.", |
| explanation = "An unknown error occured while sending LLDP " + |
| "messages to switches.", |
| recommendation = LogMessageDoc.CHECK_SWITCH) |
| }) |
| public void startUp(FloodlightModuleContext context) { |
| ScheduledExecutorService ses = threadPool.getScheduledExecutor(); |
| controller = context.getServiceImpl(IFloodlightProviderService.class); |
| |
| discoveryTask = new SingletonTask(ses, new Runnable() { |
| @Override |
| public void run() { |
| try { |
| discoverLinks(); |
| } catch (Exception e) { |
| log.error("Exception in LLDP send timer.", e); |
| } finally { |
| log.trace("Rescheduling discovery task"); |
| discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, |
| TimeUnit.SECONDS); |
| } |
| } |
| }); |
| |
| discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, TimeUnit.SECONDS); |
| |
| // Register for the OpenFlow messages we want to receive |
| floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); |
| floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this); |
| // Register for switch updates |
| floodlightProvider.addOFSwitchListener(this); |
| restApi.addRestletRoutable(new LinkDiscoveryWebRoutable()); |
| } |
| } |