From a3447938501adeb5774ed1868e1b862166941b5c Mon Sep 17 00:00:00 2001 From: Ryan Izard <rizard@g.clemson.edu> Date: Mon, 5 Oct 2015 14:27:46 -0400 Subject: [PATCH] Integrated new topology code and updated unit tests to a passing state. I'd be more comfortable adding more unit tests to the topology. Also made some changes to the link discovery manager's latency subsystem. We start with a baseline latency based on the switch's features reply turnaround time. This should be relatively quick -- just like an echo. Next step for latency is to keep a rolling list of past latencies for more defined topology updates based on latency updates. We don't want to update latencies too frequently, since tiny changes (or outliers) shouldn't require a complete topology recomputation. --- .../core/OFConnection.java | 2 +- .../core/internal/OFChannelHandler.java | 15 +- .../internal/OFSwitchHandshakeHandler.java | 7 +- .../forwarding/Forwarding.java | 172 +++----- .../linkdiscovery/ILinkDiscovery.java | 2 + .../linkdiscovery/LinkInfo.java | 2 +- .../internal/LinkDiscoveryManager.java | 73 +++- .../floodlightcontroller/routing/Link.java | 5 +- .../topology/ITopologyService.java | 21 +- .../topology/TopologyInstance.java | 395 +++++++++++++----- .../topology/TopologyManager.java | 82 +++- src/main/resources/logback-test.xml | 2 +- 12 files changed, 532 insertions(+), 246 deletions(-) diff --git a/src/main/java/net/floodlightcontroller/core/OFConnection.java b/src/main/java/net/floodlightcontroller/core/OFConnection.java index 009b31bb9..eb59851d1 100644 --- a/src/main/java/net/floodlightcontroller/core/OFConnection.java +++ b/src/main/java/net/floodlightcontroller/core/OFConnection.java @@ -434,4 +434,4 @@ public class OFConnection implements IOFConnection, IOFConnectionBackend{ } } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java index 8f3cbedb6..437d97b4f 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java @@ -87,8 +87,8 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { private long handshakeTransactionIds = 0x00FFFFFFFFL; private volatile long echoSendTime; - - + private volatile long featuresLatency; + /** * Default implementation for message handlers in any OFChannelState. * @@ -385,8 +385,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { * we send capture the features reply. * Next state is CompleteState */ - class WaitFeaturesReplyState extends OFChannelState{ - + class WaitFeaturesReplyState extends OFChannelState { WaitFeaturesReplyState() { super(false); } @@ -394,6 +393,8 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { void processOFFeaturesReply(OFFeaturesReply m) throws IOException { featuresReply = m; + + featuresLatency = (System.currentTimeMillis() - featuresLatency) / 2; // Mark handshake as completed setState(new CompleteState()); @@ -433,6 +434,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { @Override void enterState() throws IOException { sendFeaturesRequest(); + featuresLatency = System.currentTimeMillis(); } @Override @@ -474,9 +476,12 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { setAuxChannelIdle(); } } + + connection.updateLatency(U64.of(featuresLatency)); + echoSendTime = 0; + // Notify the connection broker notifyConnectionOpened(connection); - } }; diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java index 6ce132a0a..1a1993e21 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java @@ -1098,8 +1098,7 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { * we send a DescriptionStatsRequest to the switch. * Next state: WAIT_DESCRIPTION_STAT_REPLY */ - public class WaitConfigReplyState extends OFSwitchHandshakeState { - + public class WaitConfigReplyState extends OFSwitchHandshakeState { WaitConfigReplyState() { super(false); } @@ -1179,6 +1178,8 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { */ public class WaitDescriptionStatReplyState extends OFSwitchHandshakeState{ + long timestamp; + WaitDescriptionStatReplyState() { super(false); } @@ -1207,12 +1208,12 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { if (portDescStats != null) { sw.setPortDescStats(portDescStats); } + /* * Need to add after setting the features. */ switchManager.switchAdded(sw); - // Handle pending messages now that we have a sw object handlePendingPortStatusMessages(description); diff --git a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java index 7f07b1a59..32f0ff09c 100644 --- a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java +++ b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java @@ -19,17 +19,14 @@ package net.floodlightcontroller.forwarding; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.devicemanager.IDevice; -import net.floodlightcontroller.devicemanager.IDeviceService; -import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; @@ -40,6 +37,9 @@ import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.util.AppCookie; import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.devicemanager.IDevice; +import net.floodlightcontroller.devicemanager.IDeviceService; +import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.packet.IPv6; @@ -52,12 +52,13 @@ import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.topology.ITopologyService; import org.projectfloodlight.openflow.protocol.OFFlowMod; -import org.projectfloodlight.openflow.protocol.match.Match; -import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.protocol.OFFlowModCommand; import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFPacketOut; import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.EthType; import org.projectfloodlight.openflow.types.IPv4Address; @@ -69,7 +70,6 @@ import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.OFVlanVidMatch; import org.projectfloodlight.openflow.types.U64; import org.projectfloodlight.openflow.types.VlanVid; -import org.projectfloodlight.openflow.protocol.action.OFAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -136,7 +136,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd(); // this will be a drop-flow; a flow that will not output to any ports List<OFAction> actions = new ArrayList<OFAction>(); // set no action to drop U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); - + log.info("Droppingggg"); fmb.setCookie(cookie) .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) @@ -162,111 +162,82 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { // Check if we have the location of the destination IDevice dstDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_DST_DEVICE); + DatapathId source=sw.getId(); + log.info("Packet arrived on switch {}",source); + log.info("Port number {}", inPort); if (dstDevice != null) { IDevice srcDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE); - DatapathId srcIsland = topologyService.getL2DomainId(sw.getId()); + DatapathId srcIsland = topologyService.getL2DomainId(source); if (srcDevice == null) { - log.debug("No device entry found for source device"); + log.info("No device entry found for source device"); return; } if (srcIsland == null) { - log.debug("No openflow island found for source {}/{}", + log.info("No openflow island found for source {}/{}", sw.getId().toString(), inPort); return; } - // Validate that we have a destination known on the same island // Validate that the source and destination are not on the same switchport - boolean on_same_island = false; + boolean on_same_if = false; for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) { - DatapathId dstSwDpid = dstDap.getSwitchDPID(); - DatapathId dstIsland = topologyService.getL2DomainId(dstSwDpid); - log.trace("Source Island: {}, Destination Island: {}", srcIsland, dstIsland); - log.trace("Source Device: {}, Destination Device: {}", srcDevice, dstDevice); - if ((dstIsland != null) && dstIsland.equals(srcIsland)) { - on_same_island = true; - if (sw.getId().equals(dstSwDpid) && inPort.equals(dstDap.getPort())) { - on_same_if = true; - } - break; + if (sw.getId().equals(dstDap.getSwitchDPID()) && inPort.equals(dstDap.getPort())) { + on_same_if = true; } - } - - if (!on_same_island) { - // Flood since we don't know the dst device - if (log.isTraceEnabled()) { - log.trace("No first hop island found for destination " + - "device {}, Action = flooding", dstDevice); - } - doFlood(sw, pi, cntx); - return; + break; } if (on_same_if) { - if (log.isTraceEnabled()) { - log.trace("Both source and destination are on the same " + - "switch/port {}/{}, Action = NOP", - sw.toString(), inPort); - } + log.info("Both source and destination are on the same " + + "switch/port {}/{}, Action = NOP", + sw.toString(), inPort); return; } - // Install all the routes where both src and dst have attachment - // points. Since the lists are stored in sorted order we can - // traverse the attachment points in O(m+n) time - SwitchPort[] srcDaps = srcDevice.getAttachmentPoints(); - Arrays.sort(srcDaps, clusterIdComparator); SwitchPort[] dstDaps = dstDevice.getAttachmentPoints(); - Arrays.sort(dstDaps, clusterIdComparator); - - int iSrcDaps = 0, iDstDaps = 0; - - while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) { - SwitchPort srcDap = srcDaps[iSrcDaps]; - SwitchPort dstDap = dstDaps[iDstDaps]; - - // srcCluster and dstCluster here cannot be null as - // every switch will be at least in its own L2 domain. - DatapathId srcCluster = topologyService.getL2DomainId(srcDap.getSwitchDPID()); - DatapathId dstCluster = topologyService.getL2DomainId(dstDap.getSwitchDPID()); - - int srcVsDest = srcCluster.compareTo(dstCluster); - if (srcVsDest == 0) { - if (!srcDap.equals(dstDap)) { - Route route = - routingEngineService.getRoute(srcDap.getSwitchDPID(), - srcDap.getPort(), - dstDap.getSwitchDPID(), - dstDap.getPort(), U64.of(0)); //cookie = 0, i.e., default route - if (route != null) { - if (log.isTraceEnabled()) { - log.trace("pushRoute inPort={} route={} " + - "destination={}:{}", - new Object[] { inPort, route, - dstDap.getSwitchDPID(), - dstDap.getPort()}); - } - - U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); - - Match m = createMatchFromPacket(sw, inPort, cntx); - - pushRoute(route, m, pi, sw.getId(), cookie, - cntx, requestFlowRemovedNotifn, false, - OFFlowModCommand.ADD); - } - } - iSrcDaps++; - iDstDaps++; - } else if (srcVsDest < 0) { - iSrcDaps++; - } else { - iDstDaps++; + + SwitchPort dstDap = null; + + // looking for the last attachment (at the network edge) + for (SwitchPort ap : dstDaps){ + if (topologyService.isEdge(ap.getSwitchDPID(),ap.getPort())){ + dstDap = ap; + break; } + } + + if (!topologyService.isEdge(source, inPort)) { + // It's possible that we learned packed destination while it was in flight + log.info("Packet destination is known, but packet was not received on the edge port. Flooding... {}"); + doFlood(sw, pi, cntx); + return; + } + Route route = routingEngineService.getRoute(source, + inPort, + dstDap.getSwitchDPID(), + dstDap.getPort(), U64.of(0)); //cookie = 0, i.e., default route + + if (route != null) { + + log.info("pushRoute inPort={} route={} " + + "destination={}:{}", + new Object[] { inPort, route, + dstDap.getSwitchDPID(), + dstDap.getPort()}); + + + U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); + + Match m = createMatchFromPacket(sw, inPort, cntx); + log.info("Cretaing flow rules on the route, match rule: {}", m); + pushRoute(route, m, pi, sw.getId(), cookie, + cntx, requestFlowRemovedNotifn, false, + OFFlowModCommand.ADD); } } else { + log.info("Destination unknown, flooding"); // Flood since we don't know the dst device doFlood(sw, pi, cntx); } @@ -306,7 +277,6 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { } // TODO Detect switch type and match to create hardware-implemented flow - // TODO Allow for IPv6 matches if (eth.getEtherType() == EthType.IPv4) { /* shallow check for equality is okay for EthType */ IPv4 ip = (IPv4) eth.getPayload(); IPv4Address srcIp = ip.getSourceAddress(); @@ -393,25 +363,17 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { recommendation=LogMessageDoc.CHECK_SWITCH) protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); - if (topologyService.isIncomingBroadcastAllowed(sw.getId(), inPort) == false) { - if (log.isTraceEnabled()) { - log.trace("doFlood, drop broadcast packet, pi={}, " + - "from a blocked port, srcSwitch=[{},{}], linkInfo={}", - new Object[] {pi, sw.getId(), inPort}); - } - return; - } - // Set Action to flood OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); List<OFAction> actions = new ArrayList<OFAction>(); - if (sw.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) { - actions.add(sw.getOFFactory().actions().output(OFPort.FLOOD, Integer.MAX_VALUE)); // FLOOD is a more selective/efficient version of ALL - } else { - actions.add(sw.getOFFactory().actions().output(OFPort.ALL, Integer.MAX_VALUE)); + Set<OFPort> broadcastPorts = this.topologyService.getSwitchBroadcastPorts(sw.getId()); + + for (OFPort p : broadcastPorts) { + if (p.equals(inPort)) continue; + actions.add(sw.getOFFactory().actions().output(p, Integer.MAX_VALUE)); } pob.setActions(actions); - + // log.info("actions {}",actions); // set buffer-id, in-port and packet-data based on packet-in pob.setBufferId(OFBufferId.NO_BUFFER); pob.setInPort(inPort); @@ -501,7 +463,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { } tmp = configParameters.get("priority"); if (tmp != null) { - FLOWMOD_DEFAULT_PRIORITY = Integer.parseInt(tmp); + FLOWMOD_DEFAULT_PRIORITY = 35777; log.info("Default priority set to {}.", FLOWMOD_DEFAULT_PRIORITY); } else { log.info("Default priority not configured. Using {}.", FLOWMOD_DEFAULT_PRIORITY); @@ -537,4 +499,4 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { public void startUp(FloodlightModuleContext context) { super.startUp(); } -} \ No newline at end of file +} diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java index 051f2d412..20ebbc2eb 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java @@ -86,6 +86,7 @@ public interface ILinkDiscovery { this.operation = oper; this.src = switchId; this.srcType = stype; + this.latency = U64.ZERO; } // For port up or port down; and tunnel port added and removed. @@ -93,6 +94,7 @@ public interface ILinkDiscovery { this.src = sw; this.srcPort = port; this.operation = operation; + this.latency = U64.ZERO; } public DatapathId getSrc() { diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java b/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java index 6c2d7334e..ec3f7180b 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java @@ -49,7 +49,7 @@ public class LinkInfo { } protected Date firstSeenTime; - protected Date lastLldpReceivedTime; /* Standard LLLDP received time */ + protected Date lastLldpReceivedTime; /* Standard LLDP received time */ protected Date lastBddpReceivedTime; /* Modified LLDP received time */ /** The port states stored here are topology's last knowledge of diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index 26dd12e59..159f14f37 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -209,6 +209,12 @@ IFloodlightModule, IInfoProvider { protected LLDPTLV controllerTLV; protected ReentrantReadWriteLock lock; int lldpTimeCount = 0; + + /* + * Latency tracking + */ + protected static int LATENCY_HISTORY_SIZE = 4; + protected static double LATENCY_UPDATE_THRESHOLD = 0.25; /** * Flag to indicate if automatic port fast is enabled or not. Default is set @@ -362,7 +368,7 @@ IFloodlightModule, IInfoProvider { } else { lldp.getOptionalTLVList().add(forwardTLV); } - + /* * Introduce a new TLV for med-granularity link latency detection. * If same controller, can assume system clock is the same, but @@ -373,19 +379,24 @@ IFloodlightModule, IInfoProvider { * * Note Long.SIZE is in bits (64). */ + long time = System.currentTimeMillis(); + long swLatency = iofSwitch.getLatency().getValue(); + if (log.isDebugEnabled()) { + log.debug("SETTING LLDP LATENCY TLV: Current Time {}; {} control plane latency {}; sum {}", new Object[] { time, iofSwitch.getId(), swLatency, time + swLatency }); + } byte[] timestampTLVValue = ByteBuffer.allocate(Long.SIZE / 8 + 4) .put((byte) 0x00) .put((byte) 0x26) .put((byte) 0xe1) .put((byte) 0x01) /* 0x01 is what we'll use to differentiate DPID (0x00) from time (0x01) */ - .putLong(System.currentTimeMillis() + iofSwitch.getLatency().getValue() /* account for our switch's one-way latency */) + .putLong(time + swLatency /* account for our switch's one-way latency */) .array(); LLDPTLV timestampTLV = new LLDPTLV() .setType((byte) 127) .setLength((short) timestampTLVValue.length) .setValue(timestampTLVValue); - + /* Now add TLV to our LLDP packet */ lldp.getOptionalTLVList().add(timestampTLV); @@ -676,7 +687,7 @@ IFloodlightModule, IInfoProvider { OFPort remotePort = OFPort.of(portBB.getShort()); IOFSwitch remoteSwitch = null; long timestamp = 0; - + // Verify this LLDP packet matches what we're looking for for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 @@ -692,7 +703,12 @@ IFloodlightModule, IInfoProvider { && lldptlv.getValue()[2] == (byte) 0xe1 && lldptlv.getValue()[3] == 0x01) { /* 0x01 for timestamp */ ByteBuffer tsBB = ByteBuffer.wrap(lldptlv.getValue()); /* skip OpenFlow OUI (4 bytes above) */ - timestamp = tsBB.getLong(4) + iofSwitch.getLatency().getValue(); /* include the RX switch latency to "subtract" it */ + long swLatency = iofSwitch.getLatency().getValue(); + timestamp = tsBB.getLong(4); /* include the RX switch latency to "subtract" it */ + if (log.isDebugEnabled()) { + log.debug("RECEIVED LLDP LATENCY TLV: Got timestamp of {}; Switch {} latency of {}", new Object[] { timestamp, iofSwitch.getId(), iofSwitch.getLatency().getValue() }); + } + timestamp = timestamp + swLatency; } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8) { otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong(); if (myId == otherId) myLLDP = true; @@ -768,7 +784,12 @@ IFloodlightModule, IInfoProvider { // Store the time of update to this link, and push it out to // routingEngine - U64 latency = timestamp != 0 ? U64.of(System.currentTimeMillis() - timestamp) : U64.ZERO; + long time = System.currentTimeMillis(); + U64 latency = (timestamp != 0 && (time - timestamp) > 0) ? U64.of(time - timestamp) : U64.ZERO; + if (log.isDebugEnabled()) { + log.debug("COMPUTING FINAL DATAPLANE LATENCY: Current time {}; Dataplane+{} latency {}; Overall latency from {} to {} is {}", + new Object[] { time, iofSwitch.getId(), timestamp, remoteSwitch.getId(), iofSwitch.getId(), String.valueOf(latency.getValue()) }); + } Link lt = new Link(remoteSwitch.getId(), remotePort, iofSwitch.getId(), inPort, latency); /* we assume 0 latency is undefined */ @@ -1303,6 +1324,13 @@ IFloodlightModule, IInfoProvider { return true; } + /** + * Determine if a link should be updated. This includes + * @param lt + * @param oldInfo + * @param newInfo + * @return + */ protected boolean updateLink(Link lt, LinkInfo oldInfo, LinkInfo newInfo) { boolean linkChanged = false; // Since the link info is already there, we need to @@ -1331,7 +1359,12 @@ IFloodlightModule, IInfoProvider { } else if (oldTime == null & newTime != null) { linkChanged = true; } - + + /* + * TODO Update Link latency + */ + + return linkChanged; } @@ -1351,9 +1384,9 @@ IFloodlightModule, IInfoProvider { try { // put the new info. if an old info exists, it will be returned. LinkInfo oldInfo = links.put(lt, newInfo); - if (oldInfo != null - && oldInfo.getFirstSeenTime().getTime() < newInfo.getFirstSeenTime().getTime()) + if (oldInfo != null && oldInfo.getFirstSeenTime().getTime() < newInfo.getFirstSeenTime().getTime()) { newInfo.setFirstSeenTime(oldInfo.getFirstSeenTime()); + } if (log.isTraceEnabled()) { log.trace("addOrUpdateLink: {} {}", @@ -1951,9 +1984,29 @@ IFloodlightModule, IInfoProvider { EVENT_HISTORY_SIZE = Short.parseShort(histSize); } } catch (NumberFormatException e) { - log.warn("Error event history size, using default of {} seconds", EVENT_HISTORY_SIZE); + log.warn("Error event history size. Using default of {} seconds", EVENT_HISTORY_SIZE); } log.debug("Event history size set to {}", EVENT_HISTORY_SIZE); + + try { + String latencyHistorySize = configOptions.get("latency-history-size"); + if (latencyHistorySize != null) { + LATENCY_HISTORY_SIZE = Short.parseShort(latencyHistorySize); + } + } catch (NumberFormatException e) { + log.warn("Error in latency history size. Using default of {} LLDP intervals", LATENCY_HISTORY_SIZE); + } + log.info("Latency history size set to {}. Link latency updates will happen no sooner than LLDP update intervals of {}", LATENCY_HISTORY_SIZE, LATENCY_HISTORY_SIZE); + + try { + String latencyUpdateThreshold = configOptions.get("latency-update-threshold"); + if (latencyUpdateThreshold != null) { + LATENCY_UPDATE_THRESHOLD = Short.parseShort(latencyUpdateThreshold); + } + } catch (NumberFormatException e) { + log.warn("Error in latency update threshold. Can be from 0 to 1.", LATENCY_UPDATE_THRESHOLD); + } + log.info("Latency update threshold set to +/-{} ({}%) of last averaged link latency value", LATENCY_UPDATE_THRESHOLD, LATENCY_UPDATE_THRESHOLD * 100); // Set the autoportfast feature to false. this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; diff --git a/src/main/java/net/floodlightcontroller/routing/Link.java b/src/main/java/net/floodlightcontroller/routing/Link.java index a4017836c..349fb3c81 100755 --- a/src/main/java/net/floodlightcontroller/routing/Link.java +++ b/src/main/java/net/floodlightcontroller/routing/Link.java @@ -131,6 +131,8 @@ public class Link implements Comparable<Link> { + ", dst=" + this.dst.toString() + ", inPort=" + dstPort.toString() + + ", latency=" + + String.valueOf(latency.getValue()) + "]"; } @@ -158,5 +160,4 @@ public class Link implements Comparable<Link> { return this.getDstPort().compareTo(a.getDstPort()); } -} - +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/topology/ITopologyService.java b/src/main/java/net/floodlightcontroller/topology/ITopologyService.java index e1330bc70..77246df3f 100644 --- a/src/main/java/net/floodlightcontroller/topology/ITopologyService.java +++ b/src/main/java/net/floodlightcontroller/topology/ITopologyService.java @@ -17,12 +17,14 @@ package net.floodlightcontroller.topology; import java.util.Date; +import java.util.Map; import java.util.Set; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFPort; import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.routing.Link; public interface ITopologyService extends IFloodlightService { @@ -39,7 +41,22 @@ public interface ITopologyService extends IFloodlightService { public DatapathId getOpenflowDomainId(DatapathId switchId); public DatapathId getOpenflowDomainId(DatapathId switchId, boolean tunnelEnabled); - + + + /** + * Returns a complete list of the network links, including intra-cluster and inter-cluster links. Links are grouped for each DatapathId separately. + */ + public Map<DatapathId, Set<Link>> getAllLinks(); + + /** + * Checks if port "p" is edge port or belongs to one of the detected network links. + */ + public boolean isEdge(DatapathId sw, OFPort p); + + /** + * Returns list of switch ports allowed for broadcast + */ + public Set<OFPort> getSwitchBroadcastPorts(DatapathId sw); /** * Returns the identifier of the L2 domain of a given switch. * @param switchId The DPID of the switch in long form @@ -210,4 +227,4 @@ public interface ITopologyService extends IFloodlightService { * has only quarantined ports. Will never return null. */ public Set<OFPort> getPorts(DatapathId sw); -} \ No newline at end of file +} diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java b/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java index 4122bfb4c..5174531f9 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java @@ -17,6 +17,7 @@ package net.floodlightcontroller.topology; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -25,6 +26,15 @@ import java.util.Map; import java.util.PriorityQueue; import java.util.Set; +import net.floodlightcontroller.core.annotations.LogMessageCategory; +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.routing.BroadcastTree; +import net.floodlightcontroller.routing.Link; +import net.floodlightcontroller.routing.Route; +import net.floodlightcontroller.routing.RouteId; +import net.floodlightcontroller.servicechaining.ServiceChain; +import net.floodlightcontroller.util.ClusterDFS; + import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U64; @@ -35,15 +45,6 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import net.floodlightcontroller.util.ClusterDFS; -import net.floodlightcontroller.core.annotations.LogMessageCategory; -import net.floodlightcontroller.core.annotations.LogMessageDoc; -import net.floodlightcontroller.routing.BroadcastTree; -import net.floodlightcontroller.routing.Link; -import net.floodlightcontroller.routing.Route; -import net.floodlightcontroller.routing.RouteId; -import net.floodlightcontroller.servicechaining.ServiceChain; - /** * A representation of a network topology. Used internally by * {@link TopologyManager} @@ -70,7 +71,7 @@ public class TopologyInstance { protected Map<NodePortTuple, Set<Link>> switchPortLinks; // Set of links organized by node port tuple /** Set of links that are blocked. */ protected Set<Link> blockedLinks; - + protected Set<DatapathId> switches; protected Set<NodePortTuple> broadcastDomainPorts; protected Set<NodePortTuple> tunnelPorts; @@ -80,8 +81,24 @@ public class TopologyInstance { // States for routing protected Map<DatapathId, BroadcastTree> destinationRootedTrees; - protected Map<DatapathId, Set<NodePortTuple>> clusterBroadcastNodePorts; + + protected Map<DatapathId, Set<NodePortTuple>> clusterPorts; protected Map<DatapathId, BroadcastTree> clusterBroadcastTrees; + + protected Map<DatapathId, Set<NodePortTuple>> clusterBroadcastNodePorts; + //Broadcast tree over whole topology which may be consisted of multiple clusters + protected BroadcastTree finiteBroadcastTree; + //Set of NodePortTuples of the finiteBroadcastTree + protected Set<NodePortTuple> broadcastNodePorts; + //destinationRootedTrees over whole topology (not only intra-cluster tree) + protected Map<DatapathId, BroadcastTree> destinationRootedFullTrees; + //Set of all links organized by node port tuple. Note that switchPortLinks does not contain all links of multi-cluster topology. + protected Map<NodePortTuple, Set<Link>> allLinks; + //Set of all ports organized by DatapathId. Note that switchPorts map contains only ports with links. + protected Map<DatapathId, Set<OFPort>> allPorts; + // Maps broadcast ports to DatapathId + protected Map<DatapathId, Set<OFPort>> broadcastPortMap; + protected class PathCacheLoader extends CacheLoader<RouteId, Route> { TopologyInstance ti; @@ -91,7 +108,7 @@ public class TopologyInstance { @Override public Route load(RouteId rid) { - return ti.buildroute(rid); + return ti.buildroute(rid); } } @@ -99,61 +116,50 @@ public class TopologyInstance { // in the cache. private final PathCacheLoader pathCacheLoader = new PathCacheLoader(this); protected LoadingCache<RouteId, Route> pathcache; - - public TopologyInstance() { - this.switches = new HashSet<DatapathId>(); - this.switchPorts = new HashMap<DatapathId, Set<OFPort>>(); - this.switchPortLinks = new HashMap<NodePortTuple, Set<Link>>(); - this.broadcastDomainPorts = new HashSet<NodePortTuple>(); - this.tunnelPorts = new HashSet<NodePortTuple>(); - this.blockedPorts = new HashSet<NodePortTuple>(); - this.blockedLinks = new HashSet<Link>(); - } - - public TopologyInstance(Map<DatapathId, Set<OFPort>> switchPorts, - Map<NodePortTuple, Set<Link>> switchPortLinks, - Set<NodePortTuple> broadcastDomainPorts) - { - this.switches = new HashSet<DatapathId>(switchPorts.keySet()); - this.switchPorts = new HashMap<DatapathId, Set<OFPort>>(switchPorts); - this.switchPortLinks = new HashMap<NodePortTuple, - Set<Link>>(switchPortLinks); - this.broadcastDomainPorts = new HashSet<NodePortTuple>(broadcastDomainPorts); - this.tunnelPorts = new HashSet<NodePortTuple>(); - this.blockedPorts = new HashSet<NodePortTuple>(); - this.blockedLinks = new HashSet<Link>(); - - clusters = new HashSet<Cluster>(); - switchClusterMap = new HashMap<DatapathId, Cluster>(); - } + public TopologyInstance(Map<DatapathId, Set<OFPort>> switchPorts, Set<NodePortTuple> blockedPorts, Map<NodePortTuple, Set<Link>> switchPortLinks, Set<NodePortTuple> broadcastDomainPorts, - Set<NodePortTuple> tunnelPorts){ + Set<NodePortTuple> tunnelPorts, Map<NodePortTuple, Set<Link>> allLinks, Map<DatapathId, Set<OFPort>> allPorts){ // copy these structures + this.switches = new HashSet<DatapathId>(switchPorts.keySet()); this.switchPorts = new HashMap<DatapathId, Set<OFPort>>(); for(DatapathId sw: switchPorts.keySet()) { this.switchPorts.put(sw, new HashSet<OFPort>(switchPorts.get(sw))); } + + this.allPorts = new HashMap<DatapathId, Set<OFPort>>(); + for(DatapathId sw: allPorts.keySet()) { + this.allPorts.put(sw, new HashSet<OFPort>(allPorts.get(sw))); + } this.blockedPorts = new HashSet<NodePortTuple>(blockedPorts); this.switchPortLinks = new HashMap<NodePortTuple, Set<Link>>(); for(NodePortTuple npt: switchPortLinks.keySet()) { this.switchPortLinks.put(npt, new HashSet<Link>(switchPortLinks.get(npt))); + } + this.allLinks = new HashMap<NodePortTuple, Set<Link>>(); + for(NodePortTuple npt: allLinks.keySet()) { + this.allLinks.put(npt, + new HashSet<Link>(allLinks.get(npt))); } this.broadcastDomainPorts = new HashSet<NodePortTuple>(broadcastDomainPorts); this.tunnelPorts = new HashSet<NodePortTuple>(tunnelPorts); - blockedLinks = new HashSet<Link>(); - clusters = new HashSet<Cluster>(); - switchClusterMap = new HashMap<DatapathId, Cluster>(); - destinationRootedTrees = new HashMap<DatapathId, BroadcastTree>(); - clusterBroadcastTrees = new HashMap<DatapathId, BroadcastTree>(); - clusterBroadcastNodePorts = new HashMap<DatapathId, Set<NodePortTuple>>(); + this.blockedLinks = new HashSet<Link>(); + + this.clusters = new HashSet<Cluster>(); + this.switchClusterMap = new HashMap<DatapathId, Cluster>(); + this.destinationRootedTrees = new HashMap<DatapathId, BroadcastTree>(); + this.destinationRootedFullTrees= new HashMap<DatapathId, BroadcastTree>(); + this.broadcastNodePorts= new HashSet<NodePortTuple>(); + this.broadcastPortMap = new HashMap<DatapathId,Set<OFPort>>(); + this.clusterBroadcastTrees = new HashMap<DatapathId, BroadcastTree>(); + this.clusterBroadcastNodePorts = new HashMap<DatapathId, Set<NodePortTuple>>(); pathcache = CacheBuilder.newBuilder().concurrencyLevel(4) .maximumSize(1000L) @@ -164,9 +170,8 @@ public class TopologyInstance { } }); } - + public void compute() { - // Step 1: Compute clusters ignoring broadcast domain links // Create nodes for clusters in the higher level topology // Must ignore blocked links. @@ -180,29 +185,64 @@ public class TopologyInstance { // unicast routing. The trees are rooted at the destination. // Cost for tunnel links and direct links are the same. calculateShortestPathTreeInClusters(); - - // Step 3. Compute broadcast tree in each cluster. + + // Step 3. Compute broadcast tree in each cluster. // Cost for tunnel links are high to discourage use of // tunnel links. The cost is set to the number of nodes // in the cluster + 1, to use as minimum number of // clusters as possible. calculateBroadcastNodePortsInClusters(); + + // Step 4. Compute e2e shortest path trees on entire topology for unicast routing. + // The trees are rooted at the destination. + // Cost for tunnel links and direct links are the same. + calculateAllShortestPaths(); + + // Step 5. Compute broadcast tree for the whole topology (needed to avoid loops). + // Cost for tunnel links are high to discourage use of + // tunnel links. The cost is set to the number of nodes + // in the cluster + 1, to use as minimum number of + // clusters as possible. + calculateAllBroadcastNodePorts(); - // Step 4. print topology. + // Step 6. Compute set of ports for broadcasting. Edge ports are included. + calculateBroadcastPortMap(); + + // Step 7. print topology. printTopology(); } + /* + * Checks if OF port is edge port + */ + public boolean isEdge(DatapathId sw, OFPort portId){ + NodePortTuple np = new NodePortTuple(sw, portId); + if (allLinks.get(np) == null){ + return true; + } + else { + return false; + } + } + + /* + * Returns broadcast ports for the given DatapathId + */ + public Set<OFPort> swBroadcastPorts(DatapathId sw){ + + return this.broadcastPortMap.get(sw); + } + public void printTopology() { - if (log.isTraceEnabled()) { - log.trace("-----------------------------------------------"); - log.trace("Links: {}",this.switchPortLinks); - log.trace("broadcastDomainPorts: {}", broadcastDomainPorts); - log.trace("tunnelPorts: {}", tunnelPorts); - log.trace("clusters: {}", clusters); - log.trace("destinationRootedTrees: {}", destinationRootedTrees); - log.trace("clusterBroadcastNodePorts: {}", clusterBroadcastNodePorts); - log.trace("-----------------------------------------------"); - } + log.info("-----------------Topology-----------------------"); + log.info("All Links: {}", allLinks); + log.info("Broadcast Tree: {}", finiteBroadcastTree); + log.info("Broadcast Domain Ports: {}", broadcastDomainPorts); + log.info("Tunnel Ports: {}", tunnelPorts); + log.info("Clusters: {}", clusters); + log.info("Destination Rooted Full Trees: {}", destinationRootedFullTrees); + log.info("Broadcast Node Ports: {}", broadcastNodePorts); + log.info("-----------------------------------------------"); } protected void addLinksToOpenflowDomains() { @@ -480,7 +520,8 @@ public class TopologyInstance { } } - protected BroadcastTree dijkstra(Cluster c, DatapathId root, + //calculates the broadcast tree in cluster. Old version of code. + protected BroadcastTree clusterDijkstra(Cluster c, DatapathId root, Map<Link, Integer> linkCost, boolean isDstRooted) { HashMap<DatapathId, Link> nexthoplinks = new HashMap<DatapathId, Link>(); @@ -539,12 +580,120 @@ public class TopologyInstance { BroadcastTree ret = new BroadcastTree(nexthoplinks, cost); return ret; } + + + /* + * Dijkstra that calculates destination rooted trees over the entire topology. + */ + + protected BroadcastTree dijkstra(Map<DatapathId, Set<Link>> links, DatapathId root, + Map<Link, Integer> linkCost, + boolean isDstRooted) { + HashMap<DatapathId, Link> nexthoplinks = new HashMap<DatapathId, Link>(); + //HashMap<Long, Long> nexthopnodes = new HashMap<Long, Long>(); + HashMap<DatapathId, Integer> cost = new HashMap<DatapathId, Integer>(); + int w; + + for (DatapathId node: links.keySet()) { + nexthoplinks.put(node, null); + //nexthopnodes.put(node, null); + cost.put(node, MAX_PATH_WEIGHT); + } + + HashMap<DatapathId, Boolean> seen = new HashMap<DatapathId, Boolean>(); + PriorityQueue<NodeDist> nodeq = new PriorityQueue<NodeDist>(); + nodeq.add(new NodeDist(root, 0)); + cost.put(root, 0); + while (nodeq.peek() != null) { + NodeDist n = nodeq.poll(); + DatapathId cnode = n.getNode(); + int cdist = n.getDist(); + if (cdist >= MAX_PATH_WEIGHT) break; + if (seen.containsKey(cnode)) continue; + seen.put(cnode, true); + + for (Link link: links.get(cnode)) { + DatapathId neighbor; + + if (isDstRooted == true) neighbor = link.getSrc(); + else neighbor = link.getDst(); + + // links directed toward cnode will result in this condition + if (neighbor.equals(cnode)) continue; + + if (seen.containsKey(neighbor)) continue; + + if (linkCost == null || linkCost.get(link)==null) w = 1; + else w = linkCost.get(link); + int ndist = cdist + w; // the weight of the link, always 1 in current version of floodlight. + if (ndist < cost.get(neighbor)) { + cost.put(neighbor, ndist); + nexthoplinks.put(neighbor, link); + NodeDist ndTemp = new NodeDist(neighbor, ndist); + // Remove an object that's already in there. + // Note that the comparison is based on only the node id, + // and not node id and distance. + nodeq.remove(ndTemp); + // add the current object to the queue. + nodeq.add(ndTemp); + } + } + } + + BroadcastTree ret = new BroadcastTree(nexthoplinks, cost); + + return ret; + } + + /* + * Modification of the calculateShortestPathTreeInClusters (dealing with whole topology, not individual clusters) + */ + public void calculateAllShortestPaths() { + this.broadcastNodePorts.clear(); + this.destinationRootedFullTrees.clear(); + Map<Link, Integer> linkCost = new HashMap<Link, Integer>(); + int tunnel_weight = switchPorts.size() + 1; + + for(NodePortTuple npt : tunnelPorts) { + if (allLinks.get(npt) == null) continue; + for(Link link : allLinks.get(npt)) { + if (link == null) continue; + linkCost.put(link, tunnel_weight); + } + } + + Map<DatapathId, Set<Link>> linkDpidMap = new HashMap<DatapathId, Set<Link>>(); + for(DatapathId s : switches) { + if (switchPorts.get(s) == null) continue; + for (OFPort p: switchPorts.get(s)) { + NodePortTuple np = new NodePortTuple(s, p); + if (allLinks.get(np) == null) continue; + for(Link l: allLinks.get(np)) { + if(linkDpidMap.containsKey(s)) { + linkDpidMap.get(s).add(l); + } + else { + linkDpidMap.put(s, new HashSet<Link>(Arrays.asList(l))); + } + } + } + } + + for (DatapathId node : linkDpidMap.keySet()) { + BroadcastTree tree = dijkstra(linkDpidMap, node, linkCost, true); + destinationRootedFullTrees.put(node, tree); + } + //finiteBroadcastTree is randomly chosen in this implementation + if (this.destinationRootedFullTrees.size() > 0) { + this.finiteBroadcastTree = destinationRootedFullTrees.values().iterator().next(); + } + } protected void calculateShortestPathTreeInClusters() { pathcache.invalidateAll(); destinationRootedTrees.clear(); - Map<Link, Integer> linkCost = new HashMap<Link, Integer>(); + Map <Link, Integer> linkCost = new HashMap<Link, Integer>(); int tunnel_weight = switchPorts.size() + 1; for(NodePortTuple npt: tunnelPorts) { @@ -557,7 +706,7 @@ public class TopologyInstance { for(Cluster c: clusters) { for (DatapathId node : c.links.keySet()) { - BroadcastTree tree = dijkstra(c, node, linkCost, true); + BroadcastTree tree = clusterDijkstra(c, node, linkCost, true); destinationRootedTrees.put(node, tree); } } @@ -570,11 +719,49 @@ public class TopologyInstance { clusterBroadcastTrees.put(c.id, tree); } } + + protected Set<NodePortTuple> getAllBroadcastNodePorts() { + return this.broadcastNodePorts; + } + + protected void calculateAllBroadcastNodePorts() { + if (this.destinationRootedFullTrees.size() > 0) { + this.finiteBroadcastTree=destinationRootedFullTrees.values().iterator().next(); + Map<DatapathId, Link> links = finiteBroadcastTree.getLinks(); + if (links == null) return; + for(DatapathId nodeId: links.keySet()) { + Link l = links.get(nodeId); + if (l == null) continue; + NodePortTuple npt1 = new NodePortTuple(l.getSrc(), l.getSrcPort()); + NodePortTuple npt2 = new NodePortTuple(l.getDst(), l.getDstPort()); + this.broadcastNodePorts.add(npt1); + this.broadcastNodePorts.add(npt2); + } + } + } + protected void calculateBroadcastPortMap(){ + this.broadcastPortMap.clear(); + if (destinationRootedFullTrees.size() == 0) return; + + for (DatapathId sw : this.switches) { + for (OFPort p : this.allPorts.get(sw)){ + NodePortTuple npt = new NodePortTuple(sw, p); + if (isEdge(sw, p) || broadcastNodePorts.contains(npt)) { + if (broadcastPortMap.containsKey(sw)) { + broadcastPortMap.get(sw).add(p); + } + else { + broadcastPortMap.put(sw,new HashSet<OFPort>(Arrays.asList(p))); + } + } + } + } + } + protected void calculateBroadcastNodePortsInClusters() { - clusterBroadcastTrees.clear(); - + calculateBroadcastTreeInClusters(); for(Cluster c: clusters) { @@ -585,7 +772,7 @@ public class TopologyInstance { Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>(); Map<DatapathId, Link> links = tree.getLinks(); if (links == null) continue; - for(DatapathId nodeId: links.keySet()) { + for (DatapathId nodeId: links.keySet()) { Link l = links.get(nodeId); if (l == null) continue; NodePortTuple npt1 = new NodePortTuple(l.getSrc(), l.getSrcPort()); @@ -601,19 +788,19 @@ public class TopologyInstance { NodePortTuple npt; DatapathId srcId = id.getSrc(); DatapathId dstId = id.getDst(); + //set of NodePortTuples on the route + LinkedList<NodePortTuple> sPorts = new LinkedList<NodePortTuple>(); - LinkedList<NodePortTuple> switchPorts = new LinkedList<NodePortTuple>(); + if (destinationRootedFullTrees == null) return null; + if (destinationRootedFullTrees.get(dstId) == null) return null; - if (destinationRootedTrees == null) return null; - if (destinationRootedTrees.get(dstId) == null) return null; - - Map<DatapathId, Link> nexthoplinks = destinationRootedTrees.get(dstId).getLinks(); + Map<DatapathId, Link> nexthoplinks = destinationRootedFullTrees.get(dstId).getLinks(); if (!switches.contains(srcId) || !switches.contains(dstId)) { // This is a switch that is not connected to any other switch // hence there was no update for links (and hence it is not // in the network) - log.debug("buildroute: Standalone switch: {}", srcId); + log.info("buildroute: Standalone switch: {}", srcId); // The only possible non-null path for this case is // if srcId equals dstId --- and that too is an 'empty' path [] @@ -621,41 +808,39 @@ public class TopologyInstance { } else if ((nexthoplinks!=null) && (nexthoplinks.get(srcId) != null)) { while (!srcId.equals(dstId)) { Link l = nexthoplinks.get(srcId); - npt = new NodePortTuple(l.getSrc(), l.getSrcPort()); - switchPorts.addLast(npt); + sPorts.addLast(npt); npt = new NodePortTuple(l.getDst(), l.getDstPort()); - switchPorts.addLast(npt); + sPorts.addLast(npt); srcId = nexthoplinks.get(srcId).getDst(); } } // else, no path exists, and path equals null Route result = null; - if (switchPorts != null && !switchPorts.isEmpty()) { - result = new Route(id, switchPorts); + if (sPorts != null && !sPorts.isEmpty()) { + result = new Route(id, sPorts); } if (log.isTraceEnabled()) { log.trace("buildroute: {}", result); } - return result; + return result; } + /* + * Getter Functions + */ + protected int getCost(DatapathId srcId, DatapathId dstId) { BroadcastTree bt = destinationRootedTrees.get(dstId); if (bt == null) return -1; return (bt.getCost(srcId)); } - - /* - * Getter Functions - */ - + protected Set<Cluster> getClusters() { return clusters; } - // IRoutingEngineService interfaces protected boolean routeExists(DatapathId srcId, DatapathId dstId) { BroadcastTree bt = destinationRootedTrees.get(dstId); if (bt == null) return false; @@ -664,12 +849,13 @@ public class TopologyInstance { return true; } + /* + * Calculates E2E route + */ protected Route getRoute(ServiceChain sc, DatapathId srcId, OFPort srcPort, DatapathId dstId, OFPort dstPort, U64 cookie) { - - - // Return null the route source and desitnation are the - // same switchports. + // Return null if the route source and destination are the + // same switch ports. if (srcId.equals(dstId) && srcPort.equals(dstPort)) return null; @@ -692,6 +878,7 @@ public class TopologyInstance { r = new Route(id, nptList); return r; } + // NOTE: Return a null route if srcId equals dstId. The null route // need not be stored in the cache. Moreover, the LoadingCache will @@ -700,7 +887,6 @@ public class TopologyInstance { // Return null route if srcId equals dstId if (srcId.equals(dstId)) return null; - RouteId id = new RouteId(srcId, dstId); Route result = null; @@ -770,26 +956,26 @@ public class TopologyInstance { return true; } - protected boolean - isIncomingBroadcastAllowedOnSwitchPort(DatapathId sw, OFPort portId) { - if (isInternalToOpenflowDomain(sw, portId)) { - DatapathId clusterId = getOpenflowDomainId(sw); + /* + * Takes finiteBroadcastTree into account to prevent loops in the network + */ + protected boolean isIncomingBroadcastAllowedOnSwitchPort(DatapathId sw, OFPort portId) { + if (!isEdge(sw, portId)){ NodePortTuple npt = new NodePortTuple(sw, portId); - if (clusterBroadcastNodePorts.get(clusterId).contains(npt)) + if (broadcastNodePorts.contains(npt)) return true; else return false; } return true; } - public boolean isConsistent(DatapathId oldSw, OFPort oldPort, DatapathId newSw, - OFPort newPort) { + + public boolean isConsistent(DatapathId oldSw, OFPort oldPort, DatapathId newSw, OFPort newPort) { if (isInternalToOpenflowDomain(newSw, newPort)) return true; return (oldSw.equals(newSw) && oldPort.equals(newPort)); } - protected Set<NodePortTuple> - getBroadcastNodePortsInCluster(DatapathId sw) { + protected Set<NodePortTuple> getBroadcastNodePortsInCluster(DatapathId sw) { DatapathId clusterId = getOpenflowDomainId(sw); return clusterBroadcastNodePorts.get(clusterId); } @@ -826,7 +1012,7 @@ public class TopologyInstance { public Set<OFPort> getBroadcastPorts(DatapathId targetSw, DatapathId src, OFPort srcPort) { Set<OFPort> result = new HashSet<OFPort>(); DatapathId clusterId = getOpenflowDomainId(targetSw); - for(NodePortTuple npt: clusterBroadcastNodePorts.get(clusterId)) { + for(NodePortTuple npt: clusterPorts.get(clusterId)) { if (npt.getNodeId().equals(targetSw)) { result.add(npt.getPortId()); } @@ -834,14 +1020,11 @@ public class TopologyInstance { return result; } - public NodePortTuple - getAllowedOutgoingBroadcastPort(DatapathId src, OFPort srcPort, DatapathId dst, - OFPort dstPort) { + public NodePortTuple getAllowedOutgoingBroadcastPort(DatapathId src, OFPort srcPort, DatapathId dst, OFPort dstPort) { return null; } - public NodePortTuple - getAllowedIncomingBroadcastPort(DatapathId src, OFPort srcPort) { + public NodePortTuple getAllowedIncomingBroadcastPort(DatapathId src, OFPort srcPort) { return null; } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java index 4d23837bd..2741d39b1 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java @@ -17,6 +17,7 @@ package net.floodlightcontroller.topology; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -311,6 +312,8 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo // ********************** // ILinkDiscoveryListener // ********************** + + @Override public void linkDiscoveryUpdate(List<LDUpdate> updateList) { if (log.isTraceEnabled()) { @@ -326,7 +329,9 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo } ldUpdates.add(update); } - + + + // **************** // ITopologyService // **************** @@ -334,6 +339,46 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo // // ITopologyService interface methods // + + @Override + public Map<DatapathId, Set<Link>> getAllLinks(){ + + Map<DatapathId, Set<Link>> dpidLinks = new HashMap<DatapathId, Set<Link>>(); + TopologyInstance ti = getCurrentInstance(true); + Set<DatapathId> switches = ti.getSwitches(); + + for(DatapathId s: switches) { + if (this.switchPorts.get(s) == null) continue; + for (OFPort p: switchPorts.get(s)) { + NodePortTuple np = new NodePortTuple(s, p); + if (this.switchPortLinks.get(np) == null) continue; + for(Link l: this.switchPortLinks.get(np)) { + if(dpidLinks.containsKey(s)) { + dpidLinks.get(s).add(l); + } + else { + dpidLinks.put(s,new HashSet<Link>(Arrays.asList(l))); + } + + } + } + } + + return dpidLinks; + } + + @Override + public boolean isEdge(DatapathId sw, OFPort p){ + TopologyInstance ti = getCurrentInstance(true); + return ti.isEdge(sw,p); + } + + @Override + public Set<OFPort> getSwitchBroadcastPorts(DatapathId sw){ + TopologyInstance ti = getCurrentInstance(true); + return ti.swBroadcastPorts(sw); + } + @Override public Date getLastUpdateTime() { return lastUpdateTime; @@ -1125,8 +1170,8 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo switch (update.getOperation()) { case LINK_UPDATED: addOrUpdateLink(update.getSrc(), update.getSrcPort(), - update.getDst(), update.getDstPort(), update.getLatency(), - update.getType()); + update.getDst(), update.getDstPort(), + update.getLatency(), update.getType()); break; case LINK_REMOVED: removeLink(update.getSrc(), update.getSrcPort(), @@ -1173,6 +1218,8 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo public boolean createNewInstance() { return createNewInstance("internal"); } + + /** * This function computes a new topology instance. @@ -1180,12 +1227,17 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo * 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; + + Map<NodePortTuple, Set<Link>> openflowLinks; openflowLinks = new HashMap<NodePortTuple, Set<Link>>(); Set<NodePortTuple> nptList = switchPortLinks.keySet(); @@ -1219,23 +1271,33 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo removeLinkFromStructure(openflowLinks, link); } } - + //switchPorts contains only ports that are part of links. Calculation of broadcast ports needs set of all ports. + Map<DatapathId, Set<OFPort>> allPorts = new HashMap<DatapathId, Set<OFPort>>();; + for (DatapathId sw : switchPorts.keySet()){ + allPorts.put(sw, this.getPorts(sw)); + } + TopologyInstance nt = new TopologyInstance(switchPorts, blockedPorts, openflowLinks, broadcastDomainPorts, - tunnelPorts); + tunnelPorts,switchPortLinks,allPorts); + + log.info("-----------creating Topology instance-------------"); 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<DatapathId, List<NodePortTuple>>(), 0); eventCategory.newEventWithFlush(new TopologyEvent(reason, topologyInfo)); + return true; } @@ -1463,7 +1525,7 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo public void removeLink(DatapathId srcId, OFPort srcPort, DatapathId dstId, OFPort dstPort) { - Link link = new Link(srcId, srcPort, dstId, dstPort, U64.ZERO /* not needed for lookup */); + Link link = new Link(srcId, srcPort, dstId, dstPort, U64.ZERO /* does not matter for remove (not included in .equals() of Link) */); removeLink(link); } @@ -1531,4 +1593,4 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo return ports; } -} \ No newline at end of file +} diff --git a/src/main/resources/logback-test.xml b/src/main/resources/logback-test.xml index b5e3bfd80..560b32256 100644 --- a/src/main/resources/logback-test.xml +++ b/src/main/resources/logback-test.xml @@ -15,7 +15,7 @@ <logger name="net.floodlightcontroller" level="INFO"/> <logger name="org.sdnplatform" level="INFO"></logger> <logger name="net.floodlightcontroller.devicemanager" level="INFO"></logger> - <logger name="net.floodlightcontroller.linkdiscovery" level="INFO"></logger> + <logger name="net.floodlightcontroller.linkdiscovery" level="DEBUG"></logger> <logger name="net.floodlightcontroller.forwarding" level="INFO"></logger> <logger name="net.floodlightcontroller.core" level="INFO"></logger> <logger name="net.floodlightcontroller.topology" level="INFO" ></logger> -- GitLab