/**
* Copyright 2013, Big Switch Networks, Inc.
*
* 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.floodlightcontroller.topology;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.HAListenerTypeMarker;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IFloodlightProviderService.Role;
import net.floodlightcontroller.core.IHAListener;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
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.debugcounter.IDebugCounter;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType;
import net.floodlightcontroller.debugcounter.NullDebugCounter;
import net.floodlightcontroller.debugevent.IDebugEventService;
import net.floodlightcontroller.debugevent.IEventUpdater;
import net.floodlightcontroller.debugevent.NullDebugEvent;
import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn;
import net.floodlightcontroller.debugevent.IDebugEventService.EventFieldType;
import net.floodlightcontroller.debugevent.IDebugEventService.EventType;
import net.floodlightcontroller.debugevent.IDebugEventService.MaxEventsRegistered;
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.OFType;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
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 {
protected static Logger log = LoggerFactory.getLogger(TopologyManager.class);
public static final String MODULE_NAME = "topology";
public static final String CONTEXT_TUNNEL_ENABLED =
"com.bigswitch.floodlight.topologymanager.tunnelEnabled";
/**
* Role of the controller.
*/
private Role role;
/**
* 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 Set<NodePortTuple> tunnelPorts;
protected ILinkDiscoveryService linkDiscovery;
protected IThreadPoolService threadPool;
protected IFloodlightProviderService floodlightProvider;
protected IRestApiService restApi;
protected IDebugCounterService debugCounters;
// Modules that listen to our updates
protected ArrayList<ITopologyListener> topologyAware;
protected BlockingQueue<LDUpdate> ldUpdates;
// 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;
/** Flag that indicates if tunnel ports were updated or not
*/
protected boolean tunnelPortsUpdated;
protected int TOPOLOGY_COMPUTE_INTERVAL_MS = 500;
private IHAListener haListener;
/**
* Debug Counters
*/
protected static final String PACKAGE = TopologyManager.class.getPackage().getName();
protected IDebugCounter ctrIncoming;
/**
* Debug Events
*/
protected IDebugEventService debugEvents;
/*
* Topology Event Updater
*/
protected IEventUpdater<TopologyEvent> evTopology;
/**
* Topology Information exposed for a Topology related event - used inside
* the BigTopologyEvent class
*/
protected class TopologyEventInfo {
private final int numOpenflowClustersWithTunnels;
private final int numOpenflowClustersWithoutTunnels;
private final Map<Long, List<NodePortTuple>> externalPortsMap;
private final int numTunnelPorts;
public TopologyEventInfo(int numOpenflowClustersWithTunnels,
int numOpenflowClustersWithoutTunnels,
Map<Long, List<NodePortTuple>> externalPortsMap,
int numTunnelPorts) {
super();
this.numOpenflowClustersWithTunnels = numOpenflowClustersWithTunnels;
this.numOpenflowClustersWithoutTunnels = numOpenflowClustersWithoutTunnels;
this.externalPortsMap = externalPortsMap;
this.numTunnelPorts = numTunnelPorts;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("# Openflow Clusters:");
builder.append(" { With Tunnels: ");
builder.append(numOpenflowClustersWithTunnels);
builder.append(" Without Tunnels: ");
builder.append(numOpenflowClustersWithoutTunnels);
builder.append(" }");
builder.append(", # External Clusters: ");
int numExternalClusters = externalPortsMap.size();
builder.append(numExternalClusters);
if (numExternalClusters > 0) {
builder.append(" { ");
int count = 0;
for (Long extCluster : externalPortsMap.keySet()) {
builder.append("#" + extCluster + ":Ext Ports: ");
builder.append(externalPortsMap.get(extCluster).size());
if (++count < numExternalClusters) {
builder.append(", ");
} else {
builder.append(" ");
}
}
builder.append("}");
}
builder.append(", # Tunnel Ports: ");
builder.append(numTunnelPorts);
return builder.toString();
}
}
/**
* Topology Event class to track topology related events
*/
protected class TopologyEvent {
@EventColumn(name = "Reason", description = EventFieldType.STRING)
private final String reason;
@EventColumn(name = "Topology Summary")
private final TopologyEventInfo topologyInfo;
public TopologyEvent(String reason,
TopologyEventInfo topologyInfo) {
super();
this.reason = reason;
this.topologyInfo = topologyInfo;
}
}
// Getter/Setter methods
/**
* Get the time interval for the period topology updates, if any.
* The time returned is in milliseconds.
* @return
*/
public int getTopologyComputeInterval() {
return TOPOLOGY_COMPUTE_INTERVAL_MS;
}
/**
* Set the time interval for the period topology updates, if any.
* The time is in milliseconds.
* @return
*/
public void setTopologyComputeInterval(int time_ms) {
TOPOLOGY_COMPUTE_INTERVAL_MS = time_ms;
}
/**
* 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 {
if (ldUpdates.peek() != null)
updateTopology();
handleMiscellaneousPeriodicEvents();
}
catch (Exception e) {
log.error("Error in topology instance task thread", e);
} finally {
if (floodlightProvider.getRole() != Role.SLAVE)
newInstanceTask.reschedule(TOPOLOGY_COMPUTE_INTERVAL_MS,
TimeUnit.MILLISECONDS);
}
}
}
// To be used for adding any periodic events that's required by topology.
protected void handleMiscellaneousPeriodicEvents() {
return;
}
public boolean updateTopology() {
boolean newInstanceFlag;
linksUpdated = false;
dtLinksUpdated = false;
tunnelPortsUpdated = false;
List<LDUpdate> appliedUpdates = applyUpdates();
newInstanceFlag = createNewInstance("link-discovery-updates");
lastUpdateTime = new Date();
informListeners(appliedUpdates);
return newInstanceFlag;
}
// **********************
// ILinkDiscoveryListener
// **********************
@Override
public void linkDiscoveryUpdate(List<LDUpdate> updateList) {
if (log.isTraceEnabled()) {
log.trace("Queuing update: {}", updateList);
}
ldUpdates.addAll(updateList);
}
@Override
public void linkDiscoveryUpdate(LDUpdate update) {
if (log.isTraceEnabled()) {
log.trace("Queuing update: {}", update);
}
ldUpdates.add(update);
}
// ****************
// 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) {
// If the switch port is 'tun-bsn' port, it is not
// an attachment point port, irrespective of whether
// a link is found through it or not.
if (linkDiscovery.isTunnelPort(switchid, port))
return false;
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.getSwitch(switchid);
if (sw == null) return false;
return (sw.portEnabled(port));
}
@Override
public long getOpenflowDomainId(long switchId) {
return getOpenflowDomainId(switchId, true);
}
@Override
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);
}
@Override
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).
*/
@Override
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).
*/
@Override
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<Long> getSwitchesInOpenflowDomain(long switchDPID) {
return getSwitchesInOpenflowDomain(switchDPID, true);
}
@Override
public Set<Long> getSwitchesInOpenflowDomain(long switchDPID,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getSwitchesInOpenflowDomain(switchDPID);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
@Override
public Set<NodePortTuple> getBroadcastDomainPorts() {
return portBroadcastDomainLinks.keySet();
}
@Override
public Set<NodePortTuple> getTunnelPorts() {
return tunnelPorts;
}
@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;
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// ***************
// IRoutingService
// ***************
@Override
public Route getRoute(long src, long dst, long cookie) {
return getRoute(src, dst, cookie, true);
}
@Override
public Route getRoute(long src, long dst, long cookie, boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getRoute(src, dst, cookie);
}
@Override
public Route getRoute(long src, short srcPort, long dst, short dstPort, long cookie) {
return getRoute(src, srcPort, dst, dstPort, cookie, true);
}
@Override
public Route getRoute(long src, short srcPort, long dst, short dstPort, long cookie,
boolean tunnelEnabled) {
TopologyInstance ti = getCurrentInstance(tunnelEnabled);
return ti.getRoute(null, src, srcPort, dst, dstPort, cookie);
}
@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);
}
@Override
public ArrayList<Route> getRoutes(long srcDpid, long dstDpid,
boolean tunnelEnabled) {
// Floodlight supports single path routing now
// return single path now
ArrayList<Route> result=new ArrayList<Route>();
result.add(getRoute(srcDpid, dstDpid, 0, tunnelEnabled));
return result;
}
// ******************
// IOFMessageListener
// ******************
@Override
public String getName() {
return MODULE_NAME;
}
@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:
ctrIncoming.updateCounterNoFlush();
return this.processPacketInMessage(sw,
(OFPacketIn) msg, cntx);
default:
break;
}
return Command.CONTINUE;
}
// ***************
// IHAListener
// ***************
private class HAListenerDelegate implements IHAListener {
@Override
public void transitionToMaster() {
role = Role.MASTER;
log.debug("Re-computing topology due " +
"to HA change from SLAVE->MASTER");
newInstanceTask.reschedule(TOPOLOGY_COMPUTE_INTERVAL_MS,
TimeUnit.MILLISECONDS);
}
@Override
public void controllerNodeIPsChanged(
Map<String, String> curControllerNodeIPs,
Map<String, String> addedControllerNodeIPs,
Map<String, String> removedControllerNodeIPs) {
// no-op
}
@Override
public String getName() {
return TopologyManager.this.getName();
}
@Override
public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type,
String name) {
return "linkdiscovery".equals(name) ||
"tunnelmanager".equals(name);
}
@Override
public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type,
String name) {
// TODO Auto-generated method stub
return false;
}
}
// *****************
// 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);
debugCounters = context.getServiceImpl(IDebugCounterService.class);
debugEvents = context.getServiceImpl(IDebugEventService.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>>();
tunnelPorts = new HashSet<NodePortTuple>();
topologyAware = new ArrayList<ITopologyListener>();
ldUpdates = new LinkedBlockingQueue<LDUpdate>();
haListener = new HAListenerDelegate();
registerTopologyDebugCounters();
registerTopologyDebugEvents();
}
protected void registerTopologyDebugEvents() throws FloodlightModuleException {
if (debugEvents == null) {
debugEvents = new NullDebugEvent();
}
try {
evTopology =
debugEvents.registerEvent(PACKAGE, "topologyevent",
"Topology Computation",
EventType.ALWAYS_LOG,
TopologyEvent.class, 100);
} catch (MaxEventsRegistered e) {
throw new FloodlightModuleException("Max events registered", e);
}
}
@Override
public void startUp(FloodlightModuleContext context) {
clearCurrentTopology();
// Initialize role to floodlight provider role.
this.role = floodlightProvider.getRole();
ScheduledExecutorService ses = threadPool.getScheduledExecutor();
newInstanceTask = new SingletonTask(ses, new UpdateTopologyWorker());
if (role != Role.SLAVE)
newInstanceTask.reschedule(TOPOLOGY_COMPUTE_INTERVAL_MS,
TimeUnit.MILLISECONDS);
linkDiscovery.addListener(this);
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
floodlightProvider.addHAListener(this.haListener);
addRestletRoutable();
}
private void registerTopologyDebugCounters() throws FloodlightModuleException {
if (debugCounters == null) {
log.error("Debug Counter Service not found.");
debugCounters = new NullDebugCounter();
}
try {
ctrIncoming = debugCounters.registerCounter(PACKAGE, "incoming",
"All incoming packets seen by this module",
CounterType.ALWAYS_COUNT);
} catch (CounterException e) {
throw new FloodlightModuleException(e.getMessage());
}
}
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;
}
}
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);
}
}
/**
* Get the set of ports to eliminate for sending out BDDP. The method
* returns all the ports that are suppressed for link discovery on the
* switch.
* packets.
* @param sid
* @return
*/
protected Set<Short> getPortsToEliminateForBDDP(long sid) {
Set<NodePortTuple> suppressedNptList = linkDiscovery.getSuppressLLDPsInfo();
if (suppressedNptList == null) return null;
Set<Short> resultPorts = new HashSet<Short>();
for(NodePortTuple npt: suppressedNptList) {
if (npt.getNodeId() == sid) {
resultPorts.add(npt.getPortId());
}
}
return resultPorts;
}
/**
* 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.getSwitch(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);
}
}
}
Set<Short> portsToEliminate = getPortsToEliminateForBDDP(sid);
if (portsToEliminate != null) {
ports.removeAll(portsToEliminate);
}
// 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.getPayload() instanceof 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);
return Command.STOP;
} else {
return dropFilter(sw.getId(), pi, cntx);
}
}
/**
* 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 List<LDUpdate> applyUpdates() {
List<LDUpdate> appliedUpdates = new ArrayList<LDUpdate>();
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);
}
switch (update.getOperation()) {
case LINK_UPDATED:
addOrUpdateLink(update.getSrc(), update.getSrcPort(),
update.getDst(), update.getDstPort(),
update.getType());
break;
case LINK_REMOVED:
removeLink(update.getSrc(), update.getSrcPort(),
update.getDst(), update.getDstPort());
break;
case SWITCH_UPDATED:
addOrUpdateSwitch(update.getSrc());
break;
case SWITCH_REMOVED:
removeSwitch(update.getSrc());
break;
case TUNNEL_PORT_ADDED:
addTunnelPort(update.getSrc(), update.getSrcPort());
break;
case TUNNEL_PORT_REMOVED:
removeTunnelPort(update.getSrc(), update.getSrcPort());
break;
case PORT_UP: case PORT_DOWN:
break;
}
// Add to the list of applied updates.
appliedUpdates.add(update);
}
return (Collections.unmodifiableList(appliedUpdates));
}
protected void addOrUpdateSwitch(long sw) {
// nothing to do here for the time being.
return;
}
public void addTunnelPort(long sw, short port) {
NodePortTuple npt = new NodePortTuple(sw, port);
tunnelPorts.add(npt);
tunnelPortsUpdated = true;
}
public void removeTunnelPort(long sw, short port) {
NodePortTuple npt = new NodePortTuple(sw, port);
tunnelPorts.remove(npt);
tunnelPortsUpdated = true;
}
public boolean createNewInstance() {
return createNewInstance("internal");
}
/**
* 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(String reason) {
Set<NodePortTuple> blockedPorts = new HashSet<NodePortTuple>();
if (!linksUpdated) return false;
Map<NodePortTuple, Set<Link>> openflowLinks;
openflowLinks =
new HashMap<NodePortTuple, Set<Link>>();
Set<NodePortTuple> nptList = switchPortLinks.keySet();
if (nptList != null) {
for(NodePortTuple npt: nptList) {
Set<Link> linkSet = switchPortLinks.get(npt);
if (linkSet == null) continue;
openflowLinks.put(npt, new HashSet<Link>(linkSet));
}
}
// Identify all broadcast domain ports.
// Mark any port that has inconsistent set of links
// as broadcast domain ports as well.
Set<NodePortTuple> broadcastDomainPorts =
identifyBroadcastDomainPorts();
// Remove all links incident on broadcast domain ports.
for(NodePortTuple npt: broadcastDomainPorts) {
if (switchPortLinks.get(npt) == null) continue;
for(Link link: switchPortLinks.get(npt)) {
removeLinkFromStructure(openflowLinks, link);
}
}
// Remove all tunnel links.
for(NodePortTuple npt: tunnelPorts) {
if (switchPortLinks.get(npt) == null) continue;
for(Link link: switchPortLinks.get(npt)) {
removeLinkFromStructure(openflowLinks, link);
}
}
TopologyInstance nt = new TopologyInstance(switchPorts,
blockedPorts,
openflowLinks,
broadcastDomainPorts,
tunnelPorts);
nt.compute();
// We set the instances with and without tunnels to be identical.
// If needed, we may compute them differently.
currentInstance = nt;
currentInstanceWithoutTunnels = nt;
TopologyEventInfo topologyInfo =
new TopologyEventInfo(0, nt.getClusters().size(),
new HashMap<Long, List<NodePortTuple>>(),
0);
evTopology.updateEventWithFlush(new TopologyEvent(reason,
topologyInfo));
return true;
}
/**
* We expect every switch port to have at most two links. Both these
* links must be unidirectional links connecting to the same switch port.
* If not, we will mark this as a broadcast domain port.
*/
protected Set<NodePortTuple> identifyBroadcastDomainPorts() {
Set<NodePortTuple> broadcastDomainPorts =
new HashSet<NodePortTuple>();
broadcastDomainPorts.addAll(this.portBroadcastDomainLinks.keySet());
Set<NodePortTuple> additionalNpt =
new HashSet<NodePortTuple>();
// Copy switchPortLinks
Map<NodePortTuple, Set<Link>> spLinks =
new HashMap<NodePortTuple, Set<Link>>();
for(NodePortTuple npt: switchPortLinks.keySet()) {
spLinks.put(npt, new HashSet<Link>(switchPortLinks.get(npt)));
}
for(NodePortTuple npt: spLinks.keySet()) {
Set<Link> links = spLinks.get(npt);
boolean bdPort = false;
ArrayList<Link> linkArray = new ArrayList<Link>();
if (links.size() > 2) {
bdPort = true;
} else if (links.size() == 2) {
for(Link l: links) {
linkArray.add(l);
}
// now, there should be two links in [0] and [1].
Link l1 = linkArray.get(0);
Link l2 = linkArray.get(1);
// check if these two are symmetric.
if (l1.getSrc() != l2.getDst() ||
l1.getSrcPort() != l2.getDstPort() ||
l1.getDst() != l2.getSrc() ||
l1.getDstPort() != l2.getSrcPort()) {
bdPort = true;
}
}
if (bdPort && (broadcastDomainPorts.contains(npt) == false)) {
additionalNpt.add(npt);
}
}
if (additionalNpt.size() > 0) {
log.warn("The following switch ports have multiple " +
"links incident on them, so these ports will be treated " +
" as braodcast domain ports. {}", additionalNpt);
broadcastDomainPorts.addAll(additionalNpt);
}
return broadcastDomainPorts;
}
public void informListeners(List<LDUpdate> linkUpdates) {
if (role != null && role != Role.MASTER)
return;
for(int i=0; i<topologyAware.size(); ++i) {
ITopologyListener listener = topologyAware.get(i);
listener.topologyChanged(linkUpdates);
}
}
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 void removeSwitch(long sid) {
// Delete all the links in the switch, switch and all
// associated data should be deleted.
if (switchPorts.containsKey(sid) == false) return;
// Check if any tunnel ports need to be removed.
for(NodePortTuple npt: tunnelPorts) {
if (npt.getNodeId() == sid) {
removeTunnelPort(npt.getNodeId(), npt.getPortId());
}
}
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;
for(Link link: linksToRemove) {
removeLink(link);
}
}
/**
* 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;
}
protected void addOrUpdateTunnelLink(long srcId, short srcPort, long dstId,
short dstPort) {
// If you need to handle tunnel links, this is a placeholder.
}
public void addOrUpdateLink(long srcId, short srcPort, long dstId,
short dstPort, LinkType type) {
Link link = new Link(srcId, srcPort, dstId, dstPort);
if (type.equals(LinkType.MULTIHOP_LINK)) {
addPortToSwitch(srcId, srcPort);
addPortToSwitch(dstId, dstPort);
addLinkToStructure(switchPortLinks, link);
addLinkToStructure(portBroadcastDomainLinks, link);
dtLinksUpdated = removeLinkFromStructure(directLinks, link);
linksUpdated = true;
} else if (type.equals(LinkType.DIRECT_LINK)) {
addPortToSwitch(srcId, srcPort);
addPortToSwitch(dstId, dstPort);
addLinkToStructure(switchPortLinks, link);
addLinkToStructure(directLinks, link);
removeLinkFromStructure(portBroadcastDomainLinks, link);
dtLinksUpdated = true;
linksUpdated = true;
} else if (type.equals(LinkType.TUNNEL)) {
addOrUpdateTunnelLink(srcId, srcPort, dstId, dstPort);
}
}
public void removeLink(Link link) {
linksUpdated = true;
dtLinksUpdated = removeLinkFromStructure(directLinks, link);
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();
tunnelPorts.clear();
switchPortLinks.clear();
portBroadcastDomainLinks.clear();
directLinks.clear();
}
/**
* Clears the current topology. Note that this does NOT
* send out updates.
*/
public void clearCurrentTopology() {
this.clear();
linksUpdated = true;
dtLinksUpdated = true;
tunnelPortsUpdated = true;
createNewInstance("startup");
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
*/
@Override
public Set<Short> getPorts(long sw) {
IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw);
if (iofSwitch == null) return Collections.emptySet();
Collection<Short> ofpList = iofSwitch.getEnabledPortNumbers();
if (ofpList == null) return Collections.emptySet();
Set<Short> ports = new HashSet<Short>(ofpList);
Set<Short> qPorts = linkDiscovery.getQuarantinedPorts(sw);
if (qPorts != null)
ports.removeAll(qPorts);
return ports;
}
}