blob: 42c29425c25ab4d550c6b80116e5cb8dc800ae99 [file] [log] [blame]
/**
* 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.getDpid().value();
short remotePort = switchPort.getPortNumber().shortValue();
IOFSwitch remoteSwitch = floodlightProvider.getSwitches().get(
switchPort.getDpid().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) {
// Cleanup link state
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());
}
}