| 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.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 net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryListener; |
| import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryService; |
| |
| 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 || |
| update.getOperation() == UpdateOperation.LINK_ADDED ) { |
| 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; |
| } |
| } |