package net.floodlightcontroller.topology;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IFloodlightProviderService.Role;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IHAListener;
import net.floodlightcontroller.core.annotations.LogMessageCategory;
import net.floodlightcontroller.core.annotations.LogMessageDoc;
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.counter.ICounterStoreService;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
import net.floodlightcontroller.packet.BSN;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.LLDP;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.routing.IRoutingService;
import net.floodlightcontroller.routing.Link;
import net.floodlightcontroller.routing.Route;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.topology.web.TopologyWebRoutable;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPacketOut;
import org.openflow.protocol.OFPort;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.openflow.protocol.OFType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Topology manager is responsible for maintaining the controller's notion
* of the network graph, as well as implementing tools for finding routes
* through the topology.
*/
@LogMessageCategory("Network Topology")
public class TopologyManager implements
IFloodlightModule, ITopologyService,
IRoutingService, ILinkDiscoveryListener,
IOFMessageListener, IHAListener {
protected static Logger log = LoggerFactory.getLogger(TopologyManager.class);
public static final String CONTEXT_TUNNEL_ENABLED =
"com.bigswitch.floodlight.topologymanager.tunnelEnabled";
/**
* Set of ports for each switch
*/
protected Map<Long, Set<Short>> switchPorts;
/**
* Set of links organized by node port tuple
*/
protected Map<NodePortTuple, Set<Link>> switchPortLinks;
/**
* Set of direct links
*/
protected Map<NodePortTuple, Set<Link>> directLinks;
/**
* set of links that are broadcast domain links.
*/
protected Map<NodePortTuple, Set<Link>> portBroadcastDomainLinks;
/**
* set of tunnel links
*/
protected Map<NodePortTuple, Set<Link>> tunnelLinks;
protected ILinkDiscoveryService linkDiscovery;
protected IThreadPoolService threadPool;
protected IFloodlightProviderService floodlightProvider;
protected IRestApiService restApi;
// Modules that listen to our updates
protected ArrayList<ITopologyListener> topologyAware;
protected BlockingQueue<LDUpdate> ldUpdates;
protected List<LDUpdate> appliedUpdates;
// These must be accessed using getCurrentInstance(), not directly
protected TopologyInstance currentInstance;
protected TopologyInstance currentInstanceWithoutTunnels;
protected SingletonTask newInstanceTask;
private Date lastUpdateTime;
/**
* Flag that indicates if links (direct/tunnel/multihop links) were
* updated as part of LDUpdate.
*/
protected boolean linksUpdated;
/**
* Flag that indicates if direct or tunnel links were updated as
* part of LDUpdate.
*/
protected boolean dtLinksUpdated;
/**
* Thread for recomputing topology. The thread is always running,
* however the function applyUpdates() has a blocking call.
*/
@LogMessageDoc(level="ERROR",
message="Error in topology instance task thread",
explanation="An unknown error occured in the topology " +
"discovery module.",
recommendation=LogMessageDoc.CHECK_CONTROLLER)
protected class UpdateTopologyWorker implements Runnable {
@Override
public void run() {
try {
updateTopology();
}
catch (Exception e) {
log.error("Error in topology instance task thread", e);
}
}
}
public boolean updateTopology() {
boolean newInstanceFlag;
linksUpdated = false;
dtLinksUpdated = false;
applyUpdates();
newInstanceFlag = createNewInstance();
lastUpdateTime = new Date();
informListeners();
return newInstanceFlag;
}
// **********************
// ILinkDiscoveryListener
// **********************
@Override
public void linkDiscoveryUpdate(LDUpdate update) {
boolean scheduleFlag = false;
// if there's no udpates in the queue, then
// we need to schedule an update.
if (ldUpdates.peek() == null)
scheduleFlag = true;
if (log.isTraceEnabled()) {
log.trace("Queuing update: {}", update);
}
ldUpdates.add(update);
if (scheduleFlag) {
newInstanceTask.reschedule(1, TimeUnit.MICROSECONDS);
}
}
// ****************
// ITopologyService
// ****************
//
// ITopologyService interface methods
//
@Override
public Date getLastUpdateTime() {
return lastUpdateTime;
}
@Override
public void addListener(ITopologyListener listener) {
topologyAware.add(listener);
}
@Override
public boolean isAttachmentPointPort(long switchid, short port) {
return isAttachmentPointPort(switchid, port, true);
}
@Override
public boolean isAttachmentPointPort(long switchid, short port,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
// if the port is not attachment point port according to
// topology instance, then return false
if (ti.isAttachmentPointPort(switchid, port) == false)
return false;
// Check whether the port is a physical port. We should not learn
// attachment points on "special" ports.
if ((port & 0xff00) == 0xff00 && port != (short)0xfffe) return false;
// Make sure that the port is enabled.
IOFSwitch sw = floodlightProvider.getSwitches().get(switchid);
if (sw == null) return false;
return (sw.portEnabled(port));
}
public long getOpenflowDomainId(long switchId) {
return getOpenflowDomainId(switchId, true);
}
public long getOpenflowDomainId(long switchId, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getOpenflowDomainId(switchId);
}
@Override
public long getL2DomainId(long switchId) {
return getL2DomainId(switchId, true);
}
@Override
public long getL2DomainId(long switchId, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getL2DomainId(switchId);
}
@Override
public boolean inSameOpenflowDomain(long switch1, long switch2) {
return inSameOpenflowDomain(switch1, switch2, true);
}
@Override
public boolean inSameOpenflowDomain(long switch1, long switch2,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.inSameOpenflowDomain(switch1, switch2);
}
@Override
public boolean isAllowed(long sw, short portId) {
return isAllowed(sw, portId, true);
}
@Override
public boolean isAllowed(long sw, short portId, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.isAllowed(sw, portId);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
@Override
public boolean isIncomingBroadcastAllowed(long sw, short portId) {
return isIncomingBroadcastAllowed(sw, portId, true);
}
public boolean isIncomingBroadcastAllowed(long sw, short portId,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.isIncomingBroadcastAllowedOnSwitchPort(sw, portId);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/** Get all the ports connected to the switch */
@Override
public Set<Short> getPortsWithLinks(long sw) {
return getPortsWithLinks(sw, true);
}
/** Get all the ports connected to the switch */
@Override
public Set<Short> getPortsWithLinks(long sw, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getPortsWithLinks(sw);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/** Get all the ports on the target switch (targetSw) on which a
* broadcast packet must be sent from a host whose attachment point
* is on switch port (src, srcPort).
*/
public Set<Short> getBroadcastPorts(long targetSw,
long src, short srcPort) {
return getBroadcastPorts(targetSw, src, srcPort, true);
}
/** Get all the ports on the target switch (targetSw) on which a
* broadcast packet must be sent from a host whose attachment point
* is on switch port (src, srcPort).
*/
public Set<Short> getBroadcastPorts(long targetSw,
long src, short srcPort,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getBroadcastPorts(targetSw, src, srcPort);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
@Override
public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
long dst, short dstPort) {
// Use this function to redirect traffic if needed.
return getOutgoingSwitchPort(src, srcPort, dst, dstPort, true);
}
@Override
public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
long dst, short dstPort,
boolean tunnelEnabled) {
// Use this function to redirect traffic if needed.
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getOutgoingSwitchPort(src, srcPort,
dst, dstPort);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
@Override
public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
long dst, short dstPort) {
return getIncomingSwitchPort(src, srcPort, dst, dstPort, true);
}
@Override
public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
long dst, short dstPort,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getIncomingSwitchPort(src, srcPort,
dst, dstPort);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/**
* Checks if the two switchports belong to the same broadcast domain.
*/
@Override
public boolean isInSameBroadcastDomain(long s1, short p1, long s2,
short p2) {
return isInSameBroadcastDomain(s1, p1, s2, p2, true);
}
@Override
public boolean isInSameBroadcastDomain(long s1, short p1,
long s2, short p2,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.inSameBroadcastDomain(s1, p1, s2, p2);
}
/**
* Checks if the switchport is a broadcast domain port or not.
*/
@Override
public boolean isBroadcastDomainPort(long sw, short port) {
return isBroadcastDomainPort(sw, port, true);
}
@Override
public boolean isBroadcastDomainPort(long sw, short port,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.isBroadcastDomainPort(new NodePortTuple(sw, port));
}
/**
* Checks if the new attachment point port is consistent with the
* old attachment point port.
*/
@Override
public boolean isConsistent(long oldSw, short oldPort,
long newSw, short newPort) {
return isConsistent(oldSw, oldPort,
newSw, newPort, true);
}
@Override
public boolean isConsistent(long oldSw, short oldPort,
long newSw, short newPort,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.isConsistent(oldSw, oldPort, newSw, newPort);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/**
* Checks if the two switches are in the same Layer 2 domain.
*/
@Override
public boolean inSameL2Domain(long switch1, long switch2) {
return inSameL2Domain(switch1, switch2, true);
}
@Override
public boolean inSameL2Domain(long switch1, long switch2,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.inSameL2Domain(switch1, switch2);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
@Override
public NodePortTuple getAllowedOutgoingBroadcastPort(long src,
short srcPort,
long dst,
short dstPort) {
return getAllowedOutgoingBroadcastPort(src, srcPort,
dst, dstPort, true);
}
@Override
public NodePortTuple getAllowedOutgoingBroadcastPort(long src,
short srcPort,
long dst,
short dstPort,
boolean tunnelEnabled){
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getAllowedOutgoingBroadcastPort(src, srcPort,
dst, dstPort);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
@Override
public NodePortTuple
getAllowedIncomingBroadcastPort(long src, short srcPort) {
return getAllowedIncomingBroadcastPort(src,srcPort, true);
}
@Override
public NodePortTuple
getAllowedIncomingBroadcastPort(long src, short srcPort,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getAllowedIncomingBroadcastPort(src,srcPort);
}
@Override
public Set<NodePortTuple> getBroadcastDomainPorts() {
return portBroadcastDomainLinks.keySet();
}
@Override
public Set<NodePortTuple> getTunnelPorts() {
return tunnelLinks.keySet();
}
@Override
public Set<NodePortTuple> getBlockedPorts() {
Set<NodePortTuple> bp;
Set<NodePortTuple> blockedPorts =
new HashSet<NodePortTuple>();
// As we might have two topologies, simply get the union of
// both of them and send it.
bp = getCurrentInstance(true).getBlockedPorts();
if (bp != null)
blockedPorts.addAll(bp);
bp = getCurrentInstance(false).getBlockedPorts();
if (bp != null)
blockedPorts.addAll(bp);
return blockedPorts;
}
@Override
public List<LDUpdate> getLastLinkUpdates() {
return appliedUpdates;
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// ***************
// IRoutingService
// ***************
@Override
public Route getRoute(long src, long dst) {
return getRoute(src, dst, true);
}
@Override
public Route getRoute(long src, long dst, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getRoute(src, dst);
}
@Override
public Route getRoute(long src, short srcPort, long dst, short dstPort) {
return getRoute(src, srcPort, dst, dstPort, true);
}
@Override
public Route getRoute(long src, short srcPort, long dst, short dstPort,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getRoute(src, srcPort, dst, dstPort);
}
@Override
public boolean routeExists(long src, long dst) {
return routeExists(src, dst, true);
}
@Override
public boolean routeExists(long src, long dst, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.routeExists(src, dst);
}
// ******************
// IOFMessageListener
// ******************
@Override
public String getName() {
return "topology";
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
return "linkdiscovery".equals(name);
}
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
return false;
}
@Override
public Command receive(IOFSwitch sw, OFMessage msg,
FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
return this.processPacketInMessage(sw,
(OFPacketIn) msg, cntx);
default:
break;
}
return Command.CONTINUE;
}
// ***************
// IHAListener
// ***************
@Override
public void roleChanged(Role oldRole, Role newRole) {
switch(newRole) {
case MASTER:
if (oldRole == Role.SLAVE) {
log.debug("Re-computing topology due " +
"to HA change from SLAVE->MASTER");
newInstanceTask.reschedule(1, TimeUnit.MILLISECONDS);
}
break;
case SLAVE:
log.debug("Clearing topology due to " +
"HA change to SLAVE");
clearCurrentTopology();
break;
default:
break;
}
}
@Override
public void controllerNodeIPsChanged(
Map<String, String> curControllerNodeIPs,
Map<String, String> addedControllerNodeIPs,
Map<String, String> removedControllerNodeIPs) {
// no-op
}
// *****************
// IFloodlightModule
// *****************
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(ITopologyService.class);
l.add(IRoutingService.class);
return l;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
Map<Class<? extends IFloodlightService>,
IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>,
IFloodlightService>();
// We are the class that implements the service
m.put(ITopologyService.class, this);
m.put(IRoutingService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>>
getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(ILinkDiscoveryService.class);
l.add(IThreadPoolService.class);
l.add(IFloodlightProviderService.class);
l.add(ICounterStoreService.class);
l.add(IRestApiService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
linkDiscovery = context.getServiceImpl(ILinkDiscoveryService.class);
threadPool = context.getServiceImpl(IThreadPoolService.class);
floodlightProvider =
context.getServiceImpl(IFloodlightProviderService.class);
restApi = context.getServiceImpl(IRestApiService.class);
switchPorts = new HashMap<Long,Set<Short>>();
switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
directLinks = new HashMap<NodePortTuple, Set<Link>>();
portBroadcastDomainLinks = new HashMap<NodePortTuple, Set<Link>>();
tunnelLinks = new HashMap<NodePortTuple, Set<Link>>();
topologyAware = new ArrayList<ITopologyListener>();
ldUpdates = new LinkedBlockingQueue<LDUpdate>();
appliedUpdates = new ArrayList<LDUpdate>();
clearCurrentTopology();
}
@Override
public void startUp(FloodlightModuleContext context) {
ScheduledExecutorService ses = threadPool.getScheduledExecutor();
newInstanceTask = new SingletonTask(ses, new UpdateTopologyWorker());
linkDiscovery.addListener(this);
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
floodlightProvider.addHAListener(this);
addRestletRoutable();
}
protected void addRestletRoutable() {
restApi.addRestletRoutable(new TopologyWebRoutable());
}
// ****************
// Internal methods
// ****************
/**
* If the packet-in switch port is disabled for all data traffic, then
* the packet will be dropped. Otherwise, the packet will follow the
* normal processing chain.
* @param sw
* @param pi
* @param cntx
* @return
*/
protected Command dropFilter(long sw, OFPacketIn pi,
FloodlightContext cntx) {
Command result = Command.CONTINUE;
short port = pi.getInPort();
// If the input port is not allowed for data traffic, drop everything.
// BDDP packets will not reach this stage.
if (isAllowed(sw, port) == false) {
if (log.isTraceEnabled()) {
log.trace("Ignoring packet because of topology " +
"restriction on switch={}, port={}", sw, port);
result = Command.STOP;
}
}
// if sufficient information is available, then drop broadcast
// packets here as well.
return result;
}
/**
* TODO This method must be moved to a layer below forwarding
* so that anyone can use it.
* @param packetData
* @param sw
* @param ports
* @param cntx
*/
@LogMessageDoc(level="ERROR",
message="Failed to clear all flows on switch {switch}",
explanation="An I/O error occured while trying send " +
"topology discovery packet",
recommendation=LogMessageDoc.CHECK_SWITCH)
public void doMultiActionPacketOut(byte[] packetData, IOFSwitch sw,
Set<Short> ports,
FloodlightContext cntx) {
if (ports == null) return;
if (packetData == null || packetData.length <= 0) return;
OFPacketOut po =
(OFPacketOut) floodlightProvider.getOFMessageFactory().
getMessage(OFType.PACKET_OUT);
List<OFAction> actions = new ArrayList<OFAction>();
for(short p: ports) {
actions.add(new OFActionOutput(p, (short) 0));
}
// set actions
po.setActions(actions);
// set action length
po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH *
ports.size()));
// set buffer-id to BUFFER_ID_NONE
po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
// set in-port to OFPP_NONE
po.setInPort(OFPort.OFPP_NONE.getValue());
// set packet data
po.setPacketData(packetData);
// compute and set packet length.
short poLength = (short)(OFPacketOut.MINIMUM_LENGTH +
po.getActionsLength() +
packetData.length);
po.setLength(poLength);
try {
//counterStore.updatePktOutFMCounterStore(sw, po);
if (log.isTraceEnabled()) {
log.trace("write broadcast packet on switch-id={} " +
"interaces={} packet-data={} packet-out={}",
new Object[] {sw.getId(), ports, packetData, po});
}
sw.write(po, cntx);
} catch (IOException e) {
log.error("Failure writing packet out", e);
}
}
/**
* The BDDP packets are forwarded out of all the ports out of an
* openflowdomain. Get all the switches in the same openflow
* domain as the sw (disabling tunnels). Then get all the
* external switch ports and send these packets out.
* @param sw
* @param pi
* @param cntx
*/
protected void doFloodBDDP(long pinSwitch, OFPacketIn pi,
FloodlightContext cntx) {
TopologyInstance ti = getCurrentInstance(false);
Set<Long> switches = ti.getSwitchesInOpenflowDomain(pinSwitch);
if (switches == null)
{
// indicates no links are connected to the switches
switches = new HashSet<Long>();
switches.add(pinSwitch);
}
for(long sid: switches) {
IOFSwitch sw = floodlightProvider.getSwitches().get(sid);
if (sw == null) continue;
Collection<Short> enabledPorts = sw.getEnabledPortNumbers();
if (enabledPorts == null)
continue;
Set<Short> ports = new HashSet<Short>();
ports.addAll(enabledPorts);
// all the ports known to topology // without tunnels.
// out of these, we need to choose only those that are
// broadcast port, otherwise, we should eliminate.
Set<Short> portsKnownToTopo = ti.getPortsWithLinks(sid);
if (portsKnownToTopo != null) {
for(short p: portsKnownToTopo) {
NodePortTuple npt =
new NodePortTuple(sid, p);
if (ti.isBroadcastDomainPort(npt) == false) {
ports.remove(p);
}
}
}
// remove the incoming switch port
if (pinSwitch == sid) {
ports.remove(pi.getInPort());
}
// we have all the switch ports to which we need to broadcast.
doMultiActionPacketOut(pi.getPacketData(), sw, ports, cntx);
}
}
protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
FloodlightContext cntx) {
// get the packet-in switch.
Ethernet eth =
IFloodlightProviderService.bcStore.
get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
if (eth.getEtherType() == Ethernet.TYPE_BSN) {
BSN bsn = (BSN) eth.getPayload();
if (bsn == null) return Command.STOP;
if (bsn.getPayload() == null) return Command.STOP;
// It could be a packet other than BSN LLDP, therefore
// continue with the regular processing.
if (bsn.getPayload() instanceof LLDP == false)
return Command.CONTINUE;
doFloodBDDP(sw.getId(), pi, cntx);
} else {
return dropFilter(sw.getId(), pi, cntx);
}
return Command.STOP;
}
/**
* Updates concerning switch disconnect and port down are not processed.
* LinkDiscoveryManager is expected to process those messages and send
* multiple link removed messages. However, all the updates from
* LinkDiscoveryManager would be propagated to the listeners of topology.
*/
@LogMessageDoc(level="ERROR",
message="Error reading link discovery update.",
explanation="Unable to process link discovery update",
recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
public void applyUpdates() {
appliedUpdates.clear();
LDUpdate update = null;
while (ldUpdates.peek() != null) {
try {
update = ldUpdates.take();
} catch (Exception e) {
log.error("Error reading link discovery update.", e);
}
if (log.isTraceEnabled()) {
log.trace("Applying update: {}", update);
}
if (update.getOperation() == UpdateOperation.LINK_UPDATED) {
addOrUpdateLink(update.getSrc(), update.getSrcPort(),
update.getDst(), update.getDstPort(),
update.getType());
} else if (update.getOperation() == UpdateOperation.LINK_REMOVED){
removeLink(update.getSrc(), update.getSrcPort(),
update.getDst(), update.getDstPort());
}
// Add to the list of applied updates.
appliedUpdates.add(update);
}
}
/**
* This function computes a new topology.
*/
/**
* This function computes a new topology instance.
* It ignores links connected to all broadcast domain ports
* and tunnel ports. The method returns if a new instance of
* topology was created or not.
*/
protected boolean createNewInstance() {
Set<NodePortTuple> blockedPorts = new HashSet<NodePortTuple>();
if (!linksUpdated) return false;
Map<NodePortTuple, Set<Link>> openflowLinks;
openflowLinks =
new HashMap<NodePortTuple, Set<Link>>(switchPortLinks);
// Remove all tunnel links.
for(NodePortTuple npt: tunnelLinks.keySet()) {
if (openflowLinks.get(npt) != null)
openflowLinks.remove(npt);
}
// Remove all broadcast domain links.
for(NodePortTuple npt: portBroadcastDomainLinks.keySet()) {
if (openflowLinks.get(npt) != null)
openflowLinks.remove(npt);
}
TopologyInstance nt = new TopologyInstance(switchPorts,
blockedPorts,
openflowLinks,
portBroadcastDomainLinks.keySet(),
tunnelLinks.keySet());
nt.compute();
// We set the instances with and without tunnels to be identical.
// If needed, we may compute them differently.
currentInstance = nt;
currentInstanceWithoutTunnels = nt;
return true;
}
public void informListeners() {
for(int i=0; i<topologyAware.size(); ++i) {
ITopologyListener listener = topologyAware.get(i);
listener.topologyChanged();
}
}
public void addSwitch(long sid) {
if (switchPorts.containsKey(sid) == false) {
switchPorts.put(sid, new HashSet<Short>());
}
}
private void addPortToSwitch(long s, short p) {
addSwitch(s);
switchPorts.get(s).add(p);
}
public boolean removeSwitchPort(long sw, short port) {
Set<Link> linksToRemove = new HashSet<Link>();
NodePortTuple npt = new NodePortTuple(sw, port);
if (switchPortLinks.containsKey(npt) == false) return false;
linksToRemove.addAll(switchPortLinks.get(npt));
for(Link link: linksToRemove) {
removeLink(link);
}
return true;
}
public boolean removeSwitch(long sid) {
// Delete all the links in the switch, switch and all
// associated data should be deleted.
if (switchPorts.containsKey(sid) == false) return false;
Set<Link> linksToRemove = new HashSet<Link>();
for(Short p: switchPorts.get(sid)) {
NodePortTuple n1 = new NodePortTuple(sid, p);
linksToRemove.addAll(switchPortLinks.get(n1));
}
if (linksToRemove.isEmpty()) return false;
for(Link link: linksToRemove) {
removeLink(link);
}
return true;
}
/**
* Add the given link to the data structure. Returns true if a link was
* added.
* @param s
* @param l
* @return
*/
private boolean addLinkToStructure(Map<NodePortTuple,
Set<Link>> s, Link l) {
boolean result1 = false, result2 = false;
NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
if (s.get(n1) == null) {
s.put(n1, new HashSet<Link>());
}
if (s.get(n2) == null) {
s.put(n2, new HashSet<Link>());
}
result1 = s.get(n1).add(l);
result2 = s.get(n2).add(l);
return (result1 || result2);
}
/**
* Delete the given link from the data strucure. Returns true if the
* link was deleted.
* @param s
* @param l
* @return
*/
private boolean removeLinkFromStructure(Map<NodePortTuple,
Set<Link>> s, Link l) {
boolean result1 = false, result2 = false;
NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
if (s.get(n1) != null) {
result1 = s.get(n1).remove(l);
if (s.get(n1).isEmpty()) s.remove(n1);
}
if (s.get(n2) != null) {
result2 = s.get(n2).remove(l);
if (s.get(n2).isEmpty()) s.remove(n2);
}
return result1 || result2;
}
public void addOrUpdateLink(long srcId, short srcPort, long dstId,
short dstPort, LinkType type) {
boolean flag1 = false, flag2 = false;
Link link = new Link(srcId, srcPort, dstId, dstPort);
addPortToSwitch(srcId, srcPort);
addPortToSwitch(dstId, dstPort);
addLinkToStructure(switchPortLinks, link);
if (type.equals(LinkType.MULTIHOP_LINK)) {
addLinkToStructure(portBroadcastDomainLinks, link);
flag1 = removeLinkFromStructure(tunnelLinks, link);
flag2 = removeLinkFromStructure(directLinks, link);
dtLinksUpdated = flag1 || flag2;
} else if (type.equals(LinkType.TUNNEL)) {
addLinkToStructure(tunnelLinks, link);
removeLinkFromStructure(portBroadcastDomainLinks, link);
removeLinkFromStructure(directLinks, link);
dtLinksUpdated = true;
} else if (type.equals(LinkType.DIRECT_LINK)) {
addLinkToStructure(directLinks, link);
removeLinkFromStructure(tunnelLinks, link);
removeLinkFromStructure(portBroadcastDomainLinks, link);
dtLinksUpdated = true;
}
linksUpdated = true;
}
public void removeLink(Link link) {
boolean flag1 = false, flag2 = false;
flag1 = removeLinkFromStructure(directLinks, link);
flag2 = removeLinkFromStructure(tunnelLinks, link);
linksUpdated = true;
dtLinksUpdated = flag1 || flag2;
removeLinkFromStructure(portBroadcastDomainLinks, link);
removeLinkFromStructure(switchPortLinks, link);
NodePortTuple srcNpt =
new NodePortTuple(link.getSrc(), link.getSrcPort());
NodePortTuple dstNpt =
new NodePortTuple(link.getDst(), link.getDstPort());
// Remove switch ports if there are no links through those switch ports
if (switchPortLinks.get(srcNpt) == null) {
if (switchPorts.get(srcNpt.getNodeId()) != null)
switchPorts.get(srcNpt.getNodeId()).remove(srcNpt.getPortId());
}
if (switchPortLinks.get(dstNpt) == null) {
if (switchPorts.get(dstNpt.getNodeId()) != null)
switchPorts.get(dstNpt.getNodeId()).remove(dstNpt.getPortId());
}
// Remove the node if no ports are present
if (switchPorts.get(srcNpt.getNodeId())!=null &&
switchPorts.get(srcNpt.getNodeId()).isEmpty()) {
switchPorts.remove(srcNpt.getNodeId());
}
if (switchPorts.get(dstNpt.getNodeId())!=null &&
switchPorts.get(dstNpt.getNodeId()).isEmpty()) {
switchPorts.remove(dstNpt.getNodeId());
}
}
public void removeLink(long srcId, short srcPort,
long dstId, short dstPort) {
Link link = new Link(srcId, srcPort, dstId, dstPort);
removeLink(link);
}
public void clear() {
switchPorts.clear();
switchPortLinks.clear();
portBroadcastDomainLinks.clear();
tunnelLinks.clear();
directLinks.clear();
appliedUpdates.clear();
}
/**
* Clears the current topology. Note that this does NOT
* send out updates.
*/
public void clearCurrentTopology() {
this.clear();
linksUpdated = true;
dtLinksUpdated = true;
createNewInstance();
lastUpdateTime = new Date();
}
/**
* Getters. No Setters.
*/
public Map<Long, Set<Short>> getSwitchPorts() {
return switchPorts;
}
public Map<NodePortTuple, Set<Link>> getSwitchPortLinks() {
return switchPortLinks;
}
public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() {
return portBroadcastDomainLinks;
}
public TopologyInstance getCurrentInstance(boolean tunnelEnabled) {
if (tunnelEnabled)
return currentInstance;
else return this.currentInstanceWithoutTunnels;
}
public TopologyInstance getCurrentInstance() {
return this.getCurrentInstance(true);
}
/**
* Switch methods
*/
public Set<Short> getPorts(long sw) {
Set<Short> ports = new HashSet<Short>();
IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
if (iofSwitch == null) return null;
Collection<Short> ofpList = iofSwitch.getEnabledPortNumbers();
if (ofpList == null) return null;
Set<Short> qPorts = linkDiscovery.getQuarantinedPorts(sw);
if (qPorts != null)
ofpList.removeAll(qPorts);
ports.addAll(ofpList);
return ports;
}
}